Is it possible to mock a single method in an alrea

2019-05-11 19:54发布

问题:

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

回答1:

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;
    }
}


回答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.



回答3:

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