等同于Answers.RETURNS_DEEP_STUBS for mock in mockito

时间:2017-12-14 21:45:41

标签: java mocking mockito spy

我一直无法找到使用" Deep Stubs"对于Mockito间谍的捣乱方法。我现在要做的是这样的事情:

@Spy private Person person = //retrieve person

    @Test
    public void testStubbed() {
        doReturn("Neil").when(person).getName().getFirstName();
        assertEquals("Neil", person.getName().getFirstName());
    }

上面的代码编译时没有任何问题,但是在运行测试时,它无法说getName()无法返回返回类型(在本例中为Name类)。

通常,在嘲笑时,你必须使用
每个模拟对象@Mock(answer = Answers.RETURNS_DEEP_STUBS)。然而,间谍似乎没有这样的东西。

有没有人成功地使用间谍成功进行深层嘲讽?

我收到的错误如下:

String cannot be returned by getName()
getName() should return Name

Due to the nature of the syntax above problem might occur because of:
1. Multithreaded testing 
//I'm not doing multithreaded testing
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies with doReturn|Throw() family of methods 
//As shown above, I'm already using the doReturn family of methods. 

3 个答案:

答案 0 :(得分:1)

虽然我仍然想知道是否有更好的方法来做到这一点,但我想为任何想要看的人发布解决方案。

下面的解决方案可以正常工作,要求您为每个级别的依赖项创建一个新的模拟(甚至是一个真实的对象/间谍)。换句话说,不是链接方法调用来创建存根,而是单独模拟每个级别。

@Spy private Person person = //retrieve person
@Mock private Name name;

@Test
public void testStubbed() {
    doReturn(name).when(person).getName();
    doReturn("Neil").when(name).getName();
    assertEquals("Neil", person.getName().getFirstName());
}

答案 1 :(得分:0)

You can get a little closer to the deep stubs you want by using doAnswer(RETURNS_DEEP_STUBS), but you can't override arbitrarily-deep method calls without taking care to stub their parent calls. I'd stick to manual single-level-deep mocks as you do in your answer, or use even less mocking if possible.


A spy's default behavior is to delegate to its real method call, which will typically return a real object (like your Name) and not a Mockito spy. This means that you won't normally be able to change those objects' behavior using Mockito: a spy isn't really the same class as the object being spied on, but rather is a generated subclass where every field value is copied from the spied-on value. (The copying is an important feature, because a delegating spy would have very unintutitive behavior regarding this, including for method calls and field values.)

Foo foo = new Foo();
foo.intValue = 42;
foo.someObject= new SomeObject();

Foo fooSpy = Mockito.spy(foo);
// Now fooSpy.intValue is 42, fooSpy.someObject refers to the exact same
// SomeObject instance, and all of fooSpy's non-final methods are overridden to
// delegate to Mockito's behavior. Importantly, SomeObject is not a spy, and
// Mockito cannot override its behavior!

So this won't work:

doReturn("Neil").when(person).getName().getFirstName();
//   Mockito thinks this call ^^^^^^^^^ should return "Neil".

And neither will this:

doReturn("Neil").when(person.getName()).getFirstName();
//    The object here ^^^^^^^^^^^^^^^^ won't be a mock, and even if Mockito
//    could automatically make it a mock, it's not clear whether that
//    should be the same spy instance every time or a new one every time.

In your situation, I'd choose the following, in order from most preferable to least:

  1. Create a real Name object and install it using doReturn. It looks like Name is a data object (aka value object) after all, which likely means it has no dependencies, solid behavior, and difficult-to-mock state transitions. You may not be gaining anything by mocking it.

  2. Create a mock Name and install it as you do in your answer. This is particularly useful if Name is more complicated than it looks to be, or if it doesn't actually exist in the first place.

  3. Replace getName to return a deep stub...

    doAnswer(RETURNS_DEEP_STUBS).when(person).getName();
    

    ...which you can then override...

    doReturn("Neil").when(person.getName()).getFirstName();
    

    ...even for arbitrarily deep values.

    doReturn("Gaelic").when(person.getName()
                                  .getEtymology()
                                  .getFirstNameEtymology())
        .getOrigin();
    

As a final editorial, one of the hazards of partial mocks is that it makes it really hard to tell which behavior is real and which is faked; this might make it hard for you to guarantee that the behavior you're testing is prod behavior and not mock behavior. Another hazard of deep stubbing is that you may be violating the Law of Demeter by definition. If you find yourself using this kind of technique often in tests, it may be time to consider rearchitecting your system under test.

答案 2 :(得分:-1)

@Test
public void listTypeTest() throws Exception {
    doCallRealMethod().when(listType).setRecordCount(new BigInteger("556756756756"));
    listType.setRecordCount(new BigInteger("556756756756"));
    doCallRealMethod().when(listType).getRecordCount();
    assertEquals(new BigInteger("556756756756"), listType.getRecordCount());
}