There are well known scenarios like caching, pooling, etc wherein object reuse is common. Testing these cases using a framework like Mockito could run into problems. Esp if there's a need to verify the arguments sent by the Caller of a Service, where the Service is mocked.
ArgumentCaptor (mockito) fails because it keeps references to the argument obj, which due to reuse by the caller only have the last/ latest updated value.
The discussion here led to using Void Answer as one possible way to solve the issue. The following (junit-3+, mockito-1.8+, commons-lang-2.5) code explains the details.
1. Service:
public class Service {
public void serve(MutableInt value) {
System.out.println("Service.serve(): "+value);
}
2. Caller:
public class Caller {
public void callService(Service service) {
MutableInt value = new MutableInt();
value.setValue(1);
service.serve(value);
value.setValue(2);
service.serve(value);
}
...
3.Tests:
public class MutableArgsTest extends TestCase{
List<MutableInt> multiValuesWritten;
@Mock
Service service;
/**
* Failure with ArgumentCaptor
*/
public void testMutableArgsWithArgCaptorFail() {
Caller caller = new Caller();
ArgumentCaptor<MutableInt> valueCaptor =
ArgumentCaptor.forClass(MutableInt.class);
caller.callService(service);
verify(service,times(2)).serve(valueCaptor.capture());
// AssertionFailedError: expected:<[1, 2]> but was:<[2, 2]>"
assertEquals(Arrays.asList(new MutableInt(1),
new MutableInt(2)),valueCaptor.getAllValues());
}
/**
* Success with Answer
*/
public void testMutableArgsWithDoAnswer() {
Caller caller = new Caller();
doAnswer(new CaptureArgumentsWrittenAsMutableInt<Void>()).
when(service).serve(any(MutableInt.class));
caller.callService(service);
verify(service,times(2)).serve(any(MutableInt.class));
// Works!
assertEquals(new MutableInt(1),multiValuesWritten.get(0));
assertEquals(new MutableInt(2),multiValuesWritten.get(1));
}
/**
* Captures Arguments to the Service.serve() method:
* - Multiple calls to serve() happen from the same caller
* - Along with reuse of MutableInt argument objects by the caller
* - Argument value is copied to a new MutableInt object & that's captured
* @param <Void>
*/
public class CaptureArgumentsWrittenAsMutableInt<Void> implements Answer<Void>{
public Void answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
multiValuesWritten.add(new MutableInt(args[0].toString()));
return null ;
}
}
}
No comments:
Post a Comment