假设我有一个代码要测试
void myMethod()
{
byte []data = new byte[1];
data[0]='a';
output.send(42, data);
data[0]='b';
output.send(55, data);
}
我写一个测试:
testSubject.myMethod();
verify(output).send(eq(42), aryEq(new byte[]{'a'}));
verify(output).send(eq(55), aryEq(new byte[]{'b'}));
测试将失败,因为方法实现对两个调用重用相同的数组,在方法完成后不可能匹配第一个send
调用的参数,因此从技术上讲,验证语句应该在方法调用之前指定,类似于期望。
测试这些方法的正确方法是什么?
好吧,看起来Mockito在这里有点不方便。它检测方法调用并记录它(使用mock(MyOutput.class, withSettings().verboseLogging());
启用日志记录),但它存储对要传递的数组的引用,因此在您更改数组时会受到影响。然后,它认为方法调用是send(42, [98])
而不是send(42, [97])
。
一种可能的方法是使用您提到的期望。例如,您可以使用计数器并在呼叫符合预期时递增它(这实际上只是一种解决方法,而且相当讨厌):
MyOutput mock = mock(MyOutput.class, withSettings().verboseLogging());
Main subject = new Main(mock);
AtomicInteger correctCallsCounter = new AtomicInteger(0);
doAnswer(invocation -> correctCallsCounter.incrementAndGet()).when(mock).send(eq(42), aryEq(new byte[]{'a'}));
doAnswer(invocation -> correctCallsCounter.incrementAndGet()).when(mock).send(eq(55), aryEq(new byte[]{'b'}));
subject.myMethod();
assertThat(correctCallsCounter.get(), is(2));
它有效,因为当调用发生且字节数组尚未更改时会触发doAnswer
。
此解决方法的最大缺点是,它仅适用于void
方法。如果send
会返回"某些东西",那么我目前看不到解决此问题的方法。
好吧,另一个是这显然是一个相当讨厌的解决方法。
因此,如果可能的话,我建议稍微重构一下您的代码(使用新数组)。这将避免这些问题。
如果你的send
方法确实会返回一些东西,而你的myMethod
方法会依赖于它,那么你通常会这样模拟它(在这个例子中,send
应该返回一个字符串):
when(mock.send(eq(55), aryEq(new byte[]{'b'}))).thenReturn("something");
为了仍然使用上述解决方法,您可以更改doAnswer
方法来增加计数器并返回 String(无论如何您都会嘲笑它,因此它并没有那么糟糕):
doAnswer(invocation -> {
correctCallsCounter.incrementAndGet();
return "something";
}).when(mock).send(eq(42), aryEq(new byte[]{'a'}));
使用Answer
复制参数的值。 这是一些代码(它并不漂亮):
public class TestMyClass
{
private static List<byte[]> mockDataList = new ArrayList<>();
@InjectMocks
private MyClass classToTest;
private InOrder inOrder;
@Mock
private ObjectClass mockOutputClass;
@After
public void afterTest()
{
inOrder.verifyNoMoreInteractions();
verifyNoMoreInteractions(mockOutputClass);
}
@Before
public void beforeTest()
{
MockitoAnnotations.initMocks(this);
doAnswer(new Answer()
{
@Override
public Object answer(
final InvocationOnMock invocation)
throws Throwable
{
final byte[] copy;
final byte[] source = invocation.getArgument(1);
copy = new byte[source.length];
System.arraycopy(source, 0, copy, 0, source.length);
mockDataList.add(copy);
return null;
}
}).when(mockOutputClass).send(anyInt(), any(byte[].class));;
inOrder = inOrder(
mockOutputClass);
}
@Test
public void myMethod_success()
{
byte[] actualParameter;
final byte[] expectedFirstArray = { (byte)'a' };
final byte[] expectedSecondArray = { (byte)'b' };
classToTest.myMethod();
actualParameter = mockDataList.get(0);
assertArrayEquals(
expectedFirstArray,
actualParameter);
inOrder.verify(mockOutputClass).send(eq(42), any(byte[].class));
actualParameter = mockDataList.get(1);
assertArrayEquals(
expectedSecondArray,
actualParameter);
inOrder.verify(mockOutputClass).send(eq(55), any(byte[].class));
}
}
请注意,参数的值与调用的验证分开比较,但参数的顺序仍然被验证(即首先是"a"数组,然后是"b"数组)。