Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito - Mocking Concrete Classes

Given the following code:

    LinkedList list = mock(LinkedList.class);
    doCallRealMethod().when(list).clear();
    list.clear();

by executing this test, a NullPointerException is thrown from first line in LinkedList#clear:

public void clear() {
    Entry<E> e = header.next;
    while (e != header) {
        Entry<E> next = e.next;
        //Code omitted. 

but header has been instantiated before:

private transient Entry<E> header = new Entry<E>(null, null, null);

Could someone please explain what's happening during mock creation?

####### UPDATE. ######

Having read all answers especially Ajay's one, I looked into Objenesis source code and find out that it's using Reflection API to create the proxy instance (through CGLIB) and therefore bypassing all constructors in the hierarchy until java.lang.Object.

Here is the sample code to simulate the issue:

public class ReflectionConstructorTest {

    @Test
    public void testAgain() {

        try {
            //java.lang.Object default constructor
            Constructor javaLangObjectConstructor = Object.class
                    .getConstructor((Class[]) null);
            Constructor mungedConstructor = ReflectionFactory
                    .getReflectionFactory()
                    .newConstructorForSerialization(CustomClient.class, javaLangObjectConstructor);

            mungedConstructor.setAccessible(true);

            //Creates new client instance without calling its constructor
            //Thus "name" is not initialized.
            Object client = mungedConstructor.newInstance((Object[]) null);

            //this will print "CustomClient" 
            System.out.println(client.getClass());
            //this will print "CustomClient: null". name is null.
            System.out.println(client.toString());

        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}


class CustomClient {
    private String name;

    CustomClient() {
        System.out.println(this.getClass().getSimpleName() + " - Constructor");
        this.name = "My Name";
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + ": " + name;
    }
}
like image 262
mhshams Avatar asked Jan 17 '13 08:01

mhshams


1 Answers

You are only asking Mockito to call the real thing on clear, the underlying object is still a fake created by Mockito for you. If you need a real LinkedList then just use the LinkedList - only the most heated purist of BDD would tell you to mock everything around you. I mean, you are not mocking Strings are you?

Mockito author himself has said that calling the real thing should be used scarcely, usually only for testing a legacy code.

If you need to spy on the real object (track the invocations) then Mockito has a feature for this too:

List list = new LinkedList();
List spy = spy(list);

With spy, you can still stub a method if you need. It basically works like a mock, but isn't ;)

like image 89
theadam Avatar answered Oct 03 '22 05:10

theadam