单元测试的问题是,您正在尝试模拟您要测试的实际类的方法,但实际上无法调用模拟方法,因为这将返回null,除非您在该方法上声明了模拟的返回值调用的方法。通常,您只模拟外部依赖关系。
实际上,有两种创建测试对象的方法:
mock和
spy。入门之一将根据您提供的类创建一个新对象,该类的内部状态为null,并且
null在每个调用的方法上都返回。因此,您需要为方法调用定义某些返回值。
spy另一方面,如果为某些方法定义了“模拟定义”,则创建一个真实的对象并拦截方法调用。
Mockito和PowerMock提供了两种定义模拟方法的方法:
// method 1when(mockedObject.methodToMock(any(Param1.class), any(Param2.class),...) .thenReturn(answer);when(mockedObject, method(Dependency.class, "methodToMock", Parameter1.class, Parameter2.class, ...) .thenReturn(answer);
要么
// method 2doReturn(answer).when(mockedObject).methodToMock(param1, param2);
不同之处在于,
method1会执行方法的实现,而后一个则不会。如果您要处理
spy对象,这很重要,因为您有时不想执行所调用方法中的真实代码,而只是替换代码或返回预定义的值!
尽管Mockito和PowerMock提供了一个
doCallRealMethod()您可以定义的,而不是
doReturn(...)or
doThrow(...),但是它将在您的真实对象中调用并执行代码,并且忽略任何模拟的方法返回语句。但是,在您要模拟被测类的方法的情况下,此功能不是那么有用。
可以通过以下方法“覆盖”方法实现:
doAnswer(Answer<T>() { @Override public T answer(InvocationOnMock invocation) throws Throwable { ... })您可以在其中简单地声明所调用方法的逻辑。因此,您可以利用此方法返回受保护方法的模拟结果:
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(...)作为
doReturn(mockedKey).when(sut).getKey(anyString());
尝试仅
getKeyFromStream(...)使用以下内容进行修改:
doReturn(mockedKey) .when(sut).getKeyFromStream(any(InputStream.class), anyString());
如果不修改
getKey(...)系统测试中的内容(SUT),将不会实现任何效果,因为
getKey(...)将执行真正的代码。但是,如果模拟sut对象,则无法调用本
//Act节中的方法,因为这将返回null。如果你试试
doCallRealMethod().when(sut).getKey(anyString());
在模拟对象上,将调用真实方法,并且如前所述,这还将调用的实际实现,
getKeyFromStream(...)而
getKeyStream(...)无论您指定为模拟方法是什么。
正如您可能自己看到的那样,测试中的实际类的模拟方法不是那么有用,并且给您带来的负担超过了它提供的任何好处。因此,如果您想要或需要完全测试私有/受保护的方法,或者仅坚持测试公共API(我建议这样做),则取决于您或您企业的政策。尽管重构的主要目的应该是改善代码的整体设计,但是您也可以重构代码以提高可测试性。



