Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to add an interceptor to all answers when using Mockito?

Tags:

java

mockito

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 :-)

like image 647
pete83 Avatar asked Jun 30 '13 10:06

pete83


1 Answers

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.

like image 82
gontard Avatar answered Oct 28 '22 20:10

gontard