Is it possible to mock a single method in an already existing object?
Asked Answered
A

3

6

For an integration test, I need to mock a specific method in a java service client without destroying the rest of the information in it. It has no self-constructor, so a solution like this is out of the question:

private DBClient mockClient = new DBClient(alreadyExistingClient){
    @Override
    void deleteItem(Item i){
        //my stuff goes here
    }
};

Is there a way to mock the deleteItem method such that the credentials, endpoints, etc... are preserved in an existing DBClient object?

edit: mockito is not available for use in this case

Avenge answered 19/4, 2013 at 17:19 Comment(5)
mockito was not a mentioned tag, but it offers a spy routine for that purposeDelorsedelos
possible duplicate of Powermock - how to mock a specific method and leave the rest of the object as-isCalciferol
Mockito is not an available framework in this case.Avenge
If you want to do a partial mock with Powermock + EasyMock, see here: code.google.com/p/powermock/wiki/MockPartialMalevolent
That creates a new object though, one that doesn't have the internal state necessary to function like I need it to.Avenge
M
6

You can use a Dynamic Proxy to intercept any method invocation you want, so you can decide between invoking the real method or do whatever you want instead.

This is an example of how to intercept the method Set.add(), you can do exactly the same for deleteItem()

package example.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Set;

public class SetProxyFactory {

    public static Set<?> getSetProxy(final Set<?> s) {
        final ClassLoader classLoader = s.getClass().getClassLoader();
        final Class<?>[] interfaces = new Class[] {Set.class};
        final InvocationHandler invocationHandler = new InvocationHandler() {

            @Override
            public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {

                if (method.getName().equals("add")) {
                    System.out.println("add() intercepted");
                    // do/return whatever you want
                }

                // or invoke the real method
                return method.invoke(s, args);
            }
        };

        final Object proxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return (Set<?>) proxy;
    }
}
Myogenic answered 19/4, 2013 at 20:43 Comment(2)
I didn't even know this was possible, and it definitely works for the situation here. Thanks for posting this, even if I don't end up using it I learned a lot picking through it.Avenge
An interesting solution. You could still check whether the dangerous method exists before you instantiate the proxy. To make sure that it was not unwittingly renamed.Naaman
A
2

You could go lo-fi and create a sub-class of the DBClient class. To this subclass, pass the instance of DBClient you want to mock.

Use composition inside the sub-class, and delegate all method calls to the original DBClient, all except the one you want to mock. Add your mock implementation to the method you want.

This is not as reusable as a mocking framework, but should work.

DBClient mockDbClient = new DBClient() {
     private DBClient dbClientDelegate;

     public void setDelegate(DBClient dbClient) {
         dbClientDelegate = dbClient;
    }

    //override all methods. 
    //delegate to the corresponding method of the dbClientDelegate instance

    //overide the method you want to mock, add asserts for method arguments
    //return mock data as appropriate

}

mockDbClient.setDelegate(preinstantiatedDbClient);
//inject mockDbClient to test class
//call test class / method

Hope this helps.

Angola answered 19/4, 2013 at 18:11 Comment(1)
I was trying to avoid this, but it's looking like it might be the only option I have in this case. Thankfully, this is a pretty isolated situation and I'll never have to do anything like this again.Avenge
S
0

In Mockito 2+ you can use spy feature for this purpose:

    PrintStream realSystemOut = System.out;
    realSystemOut.println("XXX");

    PrintStream mockedSystemOut = Mockito.spy(realSystemOut);
    Mockito.doNothing().when(mockedSystemOut).println(Mockito.anyString());
    mockedSystemOut.println("YYY");

Output:

XXX
Shephard answered 13/6, 2018 at 13:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.