Mockito.spy对象未被识别。

huangapple go评论72阅读模式
英文:

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库中mockspy方法之间的区别。

使用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.

huangapple
  • 本文由 发表于 2020年10月24日 17:18:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/64511818.html
  • java
  • mockito
  • nullpointerexception
  • unit-testing
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定