Reputation: 29143
If I mock a method to return a new instance of some object, how can I capture the returned instance?
E.g.:
when(mock.someMethod(anyString())).thenAnswer(new Answer() {
Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return new Foo(args[0])
}
});
Obviously, I can have a field of type Foo and inside answer
set it to the new instance, but is there a nicer way? Something like ArgumentCaptor?
Upvotes: 27
Views: 41630
Reputation: 24437
I had MockitoException: Cannot call abstract real method on java object! Calling real methods is only possible when mocking non abstract method
problem with the invocationOnMock.callRealMethod()
solution because I tried it to capture the result of a Spring Repository (which is an interface) injected in my unit test with @SpyBean
Spring annotation.
So here is my working version based on a comment on an issue on Spring's GitHub repo.
@SpyBean
MyRepository myRepository;
@Test
void myTest() {
// Given
AtomicReference<MyObject> resultContainer = new AtomicReference<MyObject>();
Answer<?> defaultAnswer = Mockito
.mockingDetails(myRepository)
.getMockCreationSettings()
.getDefaultAnswer();
doAnswer(invocation -> {
var result = (MyObject) defaultAnswer.answer(invocation);
resultContainer.set(result);
return result;
}).when(myRepository).myMethod();
// When
// ... some method that calls myRepository.myMethod()
// Then
verify(myRepository).myMethod();
assertThat(resultContainer.get()).isEqualTo(...);
}
Upvotes: 0
Reputation: 2748
As an alternative to @JeffFairley's answer, make it a CompletableFuture
to support async call.
class ResultCaptor<T> extends CallsRealMethods {
@Getter
CompletableFuture<T> futureResult = new CompletableFuture<>();
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
T ret = (T) invocation.callRealMethod();
futureResult.complete(ret);
return ret;
}
}
Usage
//...
// let's capture the return values from spiedDao.find()
final ResultCaptor<QueryResult> resultCaptor = new ResultCaptor<>();
doAnswer(resultCaptor).when(spiedDao).find(any(User.class), any(Query.class));
// execute once
service.run();
assertThat(resultCaptor.getFutureResult().get(10, TimeUnit.SECONDS)).isEqualTo(/* something */);
// ...
Upvotes: 0
Reputation: 4448
Call doAnswer, then call the real method and add the returning value to a list, as the following:
final var capturedValues = new ArrayList<Integer>();
final var myObjectList = spy(new MyObject());
doAnswer(invocation -> {
final var r = invocation.callRealMethod();
capturedValues.add((Integer) r);
return r;
})
.when(myObjectList)
.mySuperMethod;
A full example:
@Test
public void test() {
// arrange
final var capturedValues = new ArrayList<Integer>();
final var myObjectList = spy(new ArrayList<>());
doAnswer(invocation -> {
final var r = invocation.callRealMethod();
capturedValues.add((Integer) r);
return r;
})
.when(myObjectList)
.size();
// act
myObjectList.size();
myObjectList.add("one");
myObjectList.size();
myObjectList.add("two");
myObjectList.size();
// assert
assertEquals(3, capturedValues.size());
assertEquals("[0, 1, 2]", capturedValues.toString());
}
Upvotes: 1
Reputation: 26894
As an alternative to @JeffFairley's answer, you can leverage AtomicReference<T>
. It will act as a Holder<T>
, but I prefer this over real holders because it's defined in Java's base framework.
// spy our dao
final Dao spiedDao = spy(dao);
// instantiate a service that does some stuff, including a database find
final Service service = new Service(spiedDao);
// let's capture the return values from spiedDao.find()
AtomicReference<QueryResult> reference = new AtomicReference<>();
doAnswer(invocation -> {
QueryResult result = (QueryResult)invocation.callRealMethod();
reference.set(result);
return result;
}).when(spiedDao).find(any(User.class), any(Query.class));
// execute once
service.run();
assertThat(reference.get()).isEqualTo(/* something */);
/// change conditions ///
// execute again
service.run();
assertThat(result.get()).isEqualTo(/* something different */);
In my opinion: ResultCaptor is cool stuff that may be integrated in Mockito in the future, is widely reusable and short in syntax. But if you need that sporadically, then few lines of a lambda can be more concise
Upvotes: 4
Reputation: 114807
Looks like you want to observe and then Answer
instances, and receive notifications each time the answer
method is called (which triggers the creation of a new Foo
). So why not invent an ObservableAnswer
class:
public abstract class ObservableAnswer implements Answer {
private Listener[] listeners; // to keep it very simple...
public ObservableAnswer(Listener...listeners) {
this.listeners = listeners;
}
@Override
public Object answer(InvocationOnMock invocation) {
Object answer = observedAnswer(invocation);
for (Listener listener:listeners) {
listener.send(answer);
}
return answer;
}
// we'll have to implement this method now
public abstract Object observedAnswer(InvocationOnMock invocation);
}
Intended use:
Listener[] myListeners = getListeners(); // some magic (as usual)
when(mock.someMethod(anyString())).thenAnswer(new ObservableAnswer(myListeners) {
Object observedAnswer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return new Foo(args[0])
}
});
Upvotes: 12
Reputation: 8324
I wanted to do something similar, but with a spied object rather than a mock. Specifically, given a spied object, I want to capture the return value. Based on Andreas_D's answer, here's what I came up with.
public class ResultCaptor<T> implements Answer {
private T result = null;
public T getResult() {
return result;
}
@Override
public T answer(InvocationOnMock invocationOnMock) throws Throwable {
result = (T) invocationOnMock.callRealMethod();
return result;
}
}
Intended usage:
// spy our dao
final Dao spiedDao = spy(dao);
// instantiate a service that does some stuff, including a database find
final Service service = new Service(spiedDao);
// let's capture the return values from spiedDao.find()
final ResultCaptor<QueryResult> resultCaptor = new ResultCaptor<>();
doAnswer(resultCaptor).when(spiedDao).find(any(User.class), any(Query.class));
// execute once
service.run();
assertThat(resultCaptor.getResult()).isEqualTo(/* something */);
/// change conditions ///
// execute again
service.run();
assertThat(resultCaptor.getResult()).isEqualTo(/* something different */);
Upvotes: 38