Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito - spying on real objects calls original method

Tags:

Imagine following code:

List list = .....
List spy = spy(list);
doThrow(new NullpointerException()).when(spy).get(0);

doThrow(....) executes list.get(0) - this makes no sense at all. I would like to define mock behaviour and not to call a method here..... am I missing something?

EDIT: List is decorated by CGLIB. When I remove CGLIB proxy Mockito works as expected. Any Idea how to solve such problem when using CGLIB proxies?

like image 313
Maciej Miklas Avatar asked Feb 02 '12 09:02

Maciej Miklas


People also ask

Does Mockito spy call real method?

A mock does not call the real method, it is just proxy for actual implementations and used to track interactions with it. A spy is a partial mock, that calls the real methods unless the method is explicitly stubbed. Since Mockito does not mock final methods, so stubbing a final method for spying will not help.

How does Mockito spy work?

A Mockito spy is a partial mock. We can mock a part of the object by stubbing few methods, while real method invocations will be used for the other. By saying so, we can conclude that calling a method on a spy will invoke the actual method, unless we explicitly stub the method, and therefore the term partial mock.

What is the difference between spy and mock in Mockito?

Mocks are used to create fully mock or dummy objects. It is mainly used in large test suites. Spies are used for creating partial or half mock objects. Like mock, spies are also used in large test suites.

How do you mock a method in Spy class?

Mockito spy() method When using the spy method, there exists a real object, and spies or stubs are created of that real object. If we don't stub a method using spy, it will call the real method behavior. The main function of the spy() method is that it overrides the specific methods of the real object.


1 Answers

import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;

import java.lang.reflect.Method;

import org.junit.Test;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MockitoSpyTest {

    @Test
    public void execTest() {

        System.out.println("*** TEST 1 ***");
        System.out.println("Test on unmodified object");
        MySet ms = new MySetImpl();
        ms.set("test value");
        System.out.println("Set contains: " + ms.get());

        // decorate ms1 with easymock
        System.out.println("\n*** TEST 2 ***");
        MySet spyMs = spy(ms);
        doThrow(new NullPointerException("my test nullpointer")).when(spyMs).get();
        System.out.println("Test decorated object with SPY");
        spyMs.set("test value");
        try {
            System.out.println("Set contains: " + spyMs.get());
        } catch (NullPointerException e) {
            System.out.println("NullPointerException - as expected");
        }

        // Enhance call with CGLIB
        System.out.println("\n*** TEST 3 ***");
        System.out.println("Test on CGLIB decorated object");
        Enhancer enc = new Enhancer();
        enc.setSuperclass(MySetImpl.class);
        enc.setInterfaces(new Class[] { MySet.class });
        enc.setCallback(new MethodInterceptor() {

            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                if ("get".equals(method.getName())) {
                    System.out.println("CGLIB decorated GET call");
                }
                return proxy.invokeSuper(obj, args);
            }
        });
        MySet ms1 = (MySet) enc.create();
        ms1.set("test value");
        System.out.println("Set contains: " + ms1.get());

        // decorate ms1 with easymock
        System.out.println("\n*** TEST 4 ***");
        System.out.println("Test on CGLIB decorated object with SPY");
        MySet spyMs1 = spy(ms1);
        doThrow(new NullPointerException("my test nullpointer")).when(spyMs1).get();
        spyMs1.set("test value");
        System.out.println("Set contains: " + spyMs1.get());
    }

    public interface MySet {
        void set(String val);

        String get();
    }

    public static class MySetImpl implements MySet {
        String val;

        public void set(String val) {
            this.val = val;
            System.out.println("Original SET call:" + val);
        }

        public String get() {

            System.out.println("Original GET call:" + val);
            return val;
        }

    }
}

Example above produces output:

*** TEST 1 ***
Test on unmodified object
Original SET call:test value
Original GET call:test value
Set contains: test value

*** TEST 2 ***
Test decorated object with SPY
Original SET call:test value
NullPointerException - as expected

*** TEST 3 ***
Test on CGLIB decorated object
Original SET call:test value
CGLIB decorated GET call
Original GET call:test value
Set contains: test value

*** TEST 4 ***
Test on CGLIB decorated object with SPY
CGLIB decorated GET call
Original GET call:test value
Original SET call:test value
CGLIB decorated GET call
Original GET call:test value
Set contains: test value

Now the TEST 2 and TEST 4 should throw NullPointerException on get call - based on mockito spy: doThrow(new NullPointerException("my test nullpointer")).when(spyMs1).get();

The "TEST 4" does not throw expected exception because it is already decorated with CGLIB - we can also see on the console that CGLIb call is being executed: GLIB decorated GET call and not call on spy object. The same effect can be achived when using Spring AOP with CGLIB proxies.

like image 188
Maciej Miklas Avatar answered Dec 01 '22 13:12

Maciej Miklas