Suppose I have a validation annotation on my Interface method to validate input arguments and return value. Is it possible at the moment (V 1.9.5) to tell Mockito to invoke this validator during the invocation process?
The background would be to prevent developers from writing unrealistic tests by mocking the given interface in a way that violates the specified validator.
So what I would want is to register something like
class MyAnswerInterceptor<T> implements AnswerInterceptor<T> {
@Override
public Answer<T> intercept(final Answer<T> answer) {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
validateArguments(invocation);
T result = answer.answer(invocation);
validateReturnValue(result);
return result;
}
}
}
}
to be called on every answer of a given mock.
Is this possible at all? I've looked into the code, also to check if I could hack in at some point (even using reflection or the like), but it seems due to entanglement of instance creation and logic, it's hardly possible to achieve what I want (i.e. stuff like MockHandler mockHandler = new MockHandlerFactory().create(settings);
makes it impossible to hook in and put custom stuff on top without patching and deploying the whole thing...)
Any insight would be highly appreciated :-)
You could achieve that by creating a custom MockMaker
.
MockMaker is an extension point that makes it possible to use custom dynamic proxies and avoid using the default cglib/asm/objenesis implementation
Our custom implementation delegates all the complex stuff to the default MockMaker
: CglibMockMaker
. It "decorates" only the createMock
method by registering on the settings
parameter an InvocationListener
. This listener will be notified when an invocation
have been done allowing use to call validateArguments
and validateReturnValue
.
import org.mockito.internal.creation.CglibMockMaker;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.MockHandler;
import org.mockito.listeners.InvocationListener;
import org.mockito.listeners.MethodInvocationReport;
import org.mockito.mock.MockCreationSettings;
import org.mockito.plugins.MockMaker;
public class ValidationMockMaker implements MockMaker {
private final MockMaker delegate = new CglibMockMaker();
public ValidationMockMaker() {
}
@Override
public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
settings.getInvocationListeners().add(new InvocationListener() {
@Override
public void reportInvocation(MethodInvocationReport methodInvocationReport) {
Invocation invocation = (Invocation) methodInvocationReport.getInvocation();
validateArguments(invocation.getArguments());
validateReturnValue(methodInvocationReport.getReturnedValue());
}
});
return delegate.createMock(settings, handler);
}
@Override
public MockHandler getHandler(Object mock) {
return delegate.getHandler(mock);
}
@Override
public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
delegate.resetMock(mock, newHandler, settings);
}
protected void validateArguments(Object... arguments) {
// Arrays.stream(arguments).forEach(Objects::requireNonNull);
}
private void validateReturnValue(Object result) {
// Objects.requireNonNull(result);
}
}
Last but not least, we need to tell to Mockito to use our implementation. This is possible by adding a file
mockito-extensions/org.mockito.plugins.MockMaker
containing our MockMaker class name:
ValidationMockMaker
See Using the extension point
section in the javadoc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With