英文:
Mockito.spy object is not being recognized
问题
我正试图测试以下方法:
@Override
public Target apply(Source source) throws MappingException {
try {
Target target = targetModelObjectFactory.create(Target.class);
mapNameToFirstName(source, target);
mapMailToEMail(source, target);
mapSourceSubEntityToTargetSubEntity(source, target);
mapPrimitiveSourceColToPrimitiveTargetCol(source, target);
mapSubEntitiesSourceColToSubEntitiesTargetCol(source, target);
mapSourceSubEntityFieldToSubEntityFetchedField(source, target);
produceProducedFieldValue(target);
setConstantFieldConstantValue(target);
return target;
} catch (Exception e) {
throw new MappingException(source, e);
}
}
这是我测试的一部分:
public void TestApply() throws MappingException, MappingOperatorCreationException, TargetModelObjectCreationException {
Source mockedSource = Mockito.mock(Source.class);
Target mockedTarget = Mockito.mock(Target.class);
TargetModelObjectFactory targetModelObjectFactory = Mockito.spy(TargetModelObjectFactory.class);
Mockito.when(targetModelObjectFactory.create(Target.class)).thenReturn(mockedTarget);
sourceToTargetMapper.apply(mockedSource);
}
我遇到问题的地方是这部分:
Target target = targetModelObjectFactory.create(Target.class);
当我在测试中调试 targetModelObjectFactory
的值时,发现它的值是 "this is not available",而且我一直收到空指针异常。
注意,targetModelObjectFactory
是一个接口。
我一直在尝试像这样做:
TargetModelObjectFactory targetModelObjectFactory1 = Mockito.mock(TargetModelObjectFactory.class, Mockito.RETURNS_DEEP_STUBS);
但仍然没有成功。
任何帮助将不胜感激。
英文:
I'm trying to test the following method:
@Override
public Target apply(Source source) throws MappingException {
try {
Target target = targetModelObjectFactory.create(Target.class);
mapNameToFirstName(source, target);
mapMailToEMail(source, target);
mapSourceSubEntityToTargetSubEntity(source, target);
mapPrimitiveSourceColToPrimitiveTargetCol(source, target);
mapSubEntitiesSourceColToSubEntitiesTargetCol(source, target);
mapSourceSubEntityFieldToSubEntityFetchedField(source, target);
produceProducedFieldValue(target);
setConstantFieldConstantValue(target);
return target;
} catch (Exception e) {
throw new MappingException(source, e);
}
}
here is part of my test:
public void TestApply() throws MappingException, MappingOperatorCreationException, TargetModelObjectCreationException {
Source mockedSource = Mockito.mock(Source.class);
Target mockedTarget = Mockito.mock(Target.class);
TargetModelObjectFactory targetModelObjectFactory = Mockito.spy(TargetModelObjectFactory.class);
Mockito.when(targetModelObjectFactory.create(Target.class)).thenReturn(mockedTarget);
sourceToTargetMapper.apply(mockedSource);
}
The problem I'm having is with this part:
Target target = targetModelObjectFactory.create(Target.class);
When debbaging targetModelObjectFactory
value inside the apply function I'm testing it's value is "'this' is not available" And I keep on getting NullPointerException
node that targetModelObjectFactory
is an interface
I've been trying to do it like this:
TargetModelObjectFactory targetModelObjectFactory1 = Mockito.mock(TargetModelObjectFactory.class,Mockito.RETURNS_DEEP_STUBS);
But still no luck
Any help would be appreciated
答案1
得分: 1
问题的主要来源是在测试中未对模拟的 targetModelObjectFactory
对象进行注入。
当您对类 TargetModelObjectFactory
进行模拟/监视时,您得到的对象根本没有传递给被测试的类,
因此它的引用为null,因此在尝试在实际上为null的引用上调用方法 create
时会抛出 NullPointerException
。
根据您的被测试类的其余部分(我只能猜测),您可以选择两种方法。第一种是构造函数注入
(通常是首选,您可以在此处阅读更多信息):
class SourceToTargetMapper {
private TargetModelObjectFactory targetModelObjectFactory;
SourceToTargetMapper(TargetModelObjectFactory targetModelObjectFactory) {
this.targetModelObjectFactory = targetModelObjectFactory;
}
}
第二种是字段注入,可以通过像 @Inject
、@Autowired
等注解来实现,具体取决于所使用的工具:
class SourceToTargetMapper {
@Autowired
private TargetModelObjectFactory targetModelObjectFactory;
}
这两种情况都可以使用Mockito轻松处理:
@Test
void constructorInjectionTest() {
TargetModelObjectFactory factory = mock(TargetModelObjectFactory.class);
SourceToTargetMapper mapper = new SourceToTargetMapper(factory);
Source source = mock(Source.class);
Target target = mock(Target.class);
when(factory.create(Target.class))
.thenReturn(target);
Target result = mapper.apply(source);
assertSame(target, result);
}
@Mock
TargetModelObjectFactory factory;
@InjectMocks
private SourceToTargetMapper mapper;
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
void fieldInjectionTest() {
Source source = mock(Source.class);
Target target = mock(Target.class);
when(factory.create(Target.class))
.thenReturn(target);
Target result = mapper.apply(source);
assertSame(target, result);
}
另一个值得注意的事项是Mockito库中mock
和spy
方法之间的区别。
使用mock
时,整个类的行为都由Mockito处理,这就是为什么我们要用 Class
参数来调用它:
FirstClass firstObject = mock(FirstClass.class);
SecondClass secondObject = mock(SecondClass.class);
既不会创建FirstClass
实例,也不会创建SecondClass
实例。
而使用spy
时,我们明确告诉Mockito哪些方法的行为应该更改,哪些方法应该根据类中定义的实际情况调用。
我们可以使用Class
参数或实际对象来创建spy(后者更常用)。在spy的情况下,实际行为通常根本不会更改,因为spy可用于检查方法是否实际调用:
MyClass object = spy(new MyClass());
doStuff(object);
verify(object, times(1))
.myMethod();
在您的情况下,由于您更改了 TargetModelObjectFactory
类的行为,使用 mock
可能是更好的选择。
我在GitHub上创建了一个仓库,您可以在其中找到所有的代码 - 所有测试都通过了。
英文:
The main source of the problem is lack of injection of the mocked targetModelObjectFactory
object in the test.
When you're mocking/spying on the class TargetModelObjectFactory
, the object you get is in no way passed to the tested class,
thus it's reference is null, hence NullPointerException
is thrown when trying to call the method create
on an actually null reference.
Depending on the rest of your tested class (I can only guess) you can choose two approaches. The first one is constructor injection
(usually preferable, you can read more here):
class SourceToTargetMapper {
private TargetModelObjectFactory targetModelObjectFactory;
SourceToTargetMapper(TargetModelObjectFactory targetModelObjectFactory) {
this.targetModelObjectFactory = targetModelObjectFactory;
}
}
The second one is field injection and is possible thanks to annotations like @Inject
, @Autowired
etc., depending on the used tool:
class SourceToTargetMapper {
@Autowired
private TargetModelObjectFactory targetModelObjectFactory;
}
Both cases can be easily handled using Mockito:
@Test
void constructorInjectionTest() {
TargetModelObjectFactory factory = mock(TargetModelObjectFactory.class);
SourceToTargetMapper mapper = new SourceToTargetMapper(factory);
Source source = mock(Source.class);
Target target = mock(Target.class);
when(factory.create(Target.class))
.thenReturn(target);
Target result = mapper.apply(source);
assertSame(target, result);
}
<!-- -->
@Mock
TargetModelObjectFactory factory;
@InjectMocks
private SourceToTargetMapper mapper;
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
void fieldInjectionTest() {
Source source = mock(Source.class);
Target target = mock(Target.class);
when(factory.create(Target.class))
.thenReturn(target);
Target result = mapper.apply(source);
assertSame(target, result);
}
Another thing worth noting is the difference between mock
and spy
methods from the Mockito library.
When using mock
, the whole behaviour of the class is handled by Mockito, that's why we call it with a Class
parameter:
FirstClass firstObject = mock(FirstClass.class);
SecondClass secondObject = mock(SecondClass.class);
No actual instance of neither FirstClass
nor SecondClass
is created.
When using spy
, we explicitly tell Mockito, which methods should have their behaviour changed and which methods should be actually called
as defined in the class. We can create the spy using a Class
parameter or an actual object (the latter used more often).
In case of spies, actual behaviour is often not changed at all, since spies can be used to check if the method was actually called:
MyClass object = spy(new MyClass());
doStuff(object);
verify(object, times(1))
.myMethod();
In your case, since you change the behaviour of the TargetModelObjectFactory
class, probably mock
will be a better choice.
I've created a repository on GitHub, where you can find all the code - all tests pass.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论