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 值。通常,您只模拟外部依赖项。
实际上有两种创建测试对象的方法:mock
和spy
。入门者将根据您提供的 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.
我正在尝试使用 '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 值。通常,您只模拟外部依赖项。
实际上有两种创建测试对象的方法:mock
和spy
。入门者将根据您提供的 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.