Mockito when/then 未返回预期值

Mockito when/then not returning expected value

我正在尝试使用 'any' 匹配器对这个 getKeyFromStream 方法进行存根。我尝试过更明确和更不明确 (anyObject()),但似乎无论我尝试什么,这个存根都不会 return 我的单元测试中的 fooKey。

我想知道是不是因为它受到了保护,还是我遗漏了什么或做错了什么。在整个测试过程中,我还有其他 when/then 声明 正在工作 但出于某种原因,它不是。

注意:getKeyFromStream一般使用byteArrayInputStream,但是我想用InputStream来匹配,我都试过了都没有用。

public class FooKeyRetriever() //Mocked this guy
{
    public FooKey getKey(String keyName) throws KeyException {

        return getKeyFromStream(getKeyStream(keyName, false), keyName);
    }

    //Stubbed this method to return a key object which has been mocked
    protected FooKey getKeyFromStream(InputStream keyStream, String keyName){
        //Some code
        return fooKey;
    }
}

单元测试

@Mock
private FooKeyRetriever mockKeyRetriever;

@Mock
private FooKey fooKey;

@Before
public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
}

@Test
public void testGetFooKey() throws Exception {



    when(foo.getKeyFromStream(any(InputStream.class),any(String.class))).thenReturn(fooKey);

    FooKey fooKey = mockKeyRetriever.getKey("irrelevant_key");

    assertNotNull(fooKey);
}

你的单元测试的问题是,你试图模拟你想要测试的实际 class 的方法,但你不能实际调用模拟方法,因为这将 return null 除非您在该调用的方法上声明一个模拟的 return 值。通常,您只模拟外部依赖项。

实际上有两种创建测试对象的方法:mockspy。入门者将根据您提供的 class 创建一个新对象,该对象具有内部状态 null 并且在每个调用的方法上也具有 return null 。这就是为什么您需要为方法调用定义某些 return 值。 spy 另一方面,如果为某些方法定义了 "mock definitions",则创建一个真实的对象并拦截方法调用。

Mockito 和 PowerMock 提供了两种定义模拟方法的方法:

// method 1
when(mockedObject.methodToMock(any(Param1.class), any(Param2.class),...)
    .thenReturn(answer);
when(mockedObject, method(Dependency.class, "methodToMock", Parameter1.class, Parameter2.class, ...)
    .thenReturn(answer);

// method 2
doReturn(answer).when(mockedObject).methodToMock(param1, param2);

区别在于,method 1 将执行方法实现,而后者则不会。如果您处理 spy 对象,这一点很重要,因为有时您不想在调用的方法中执行真正的代码,而只是替换代码或 return 预定义的值!

尽管 Mockito 和 PowerMock 提供了一个 doCallRealMethod(),您可以定义它而不是 doReturn(...)doThrow(...),这将在您的真实对象中调用和执行代码并忽略任何模拟方法return 语句。但是,这在您想要模拟被测 class 的方法的情况下并不是那么有用。

方法实现可以 "overwritten" 由

doAnswer(Answer<T>() { 
    @Override 
    public T answer(InvocationOnMock invocation) throws Throwable {
        ...
    }
)

在这里您可以简单地声明被调用方法的逻辑应该是什么。您可以利用它来 return 受保护方法的模拟结果,因此像这样:

import static org.hamcrest.core.IsSame.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;

import java.io.InputStream;

import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class FooKeyRetrieverTest {

    @Test
    public void testGetFooKey() throws Exception {
        // Arrange
        final FooKeyRetriever sut = spy(new FooKeyRetriever());
        FooKey mockedKey = mock(FooKey.class);

        doReturn(mockedKey)
            .when(sut).getKeyFromStream(any(InputStream.class), anyString());
        doAnswer(new Answer<FooKey>() {

            public FooKey answer(InvocationOnMock invocation) throws Throwable {
                return sut.getKeyFromStream(null, "");
            }
        }).when(sut).getKey(anyString());

        // Act
        FooKey ret = sut.getKey("test");

        // Assert
        assertThat(ret, sameInstance(mockedKey));
    }
}

上面的代码有效,但是请注意,这与简单地将 getKey(...) 的 return 值声明为

具有相同的语义
doReturn(mockedKey).when(sut).getKey(anyString());

尝试只修改 getKeyFromStream(...) 像这样:

doReturn(mockedKey)
    .when(sut).getKeyFromStream(any(InputStream.class), anyString());

不修改您的被测系统 (SUT) 的 getKey(...) 将不会实现任何目标,因为将执行 getKey(...) 的真实代码。但是,如果您模拟 sut 对象,则无法调用 // Act 部分中的方法,因为这会使 return 为 null。如果你尝试

doCallRealMethod().when(sut).getKey(anyString());

在模拟对象上,将调用真正的方法,并且如前所述,这也会调用 getKeyFromStream(...)getKeyStream(...) 的实际实现,无论您将什么指定为模拟方法。

正如您自己可能看到的那样,模拟您实际 class 测试中的方法并不是那么有用,并且给您带来的负担比它提供的任何好处都多。因此,如果您想要或需要测试 private/protected 方法,或者您是否坚持只测试 public API(我会推荐),则取决于您或您的企业的政策.您还可以 refactor your code in order to improve testability although the primary intent of refactoring should be to improve the overall design of your code.