How Do I Convert Partial Mocks From Mockito to JMockit?

916 views Asked by At

I had some test code using Mockito (see below), however, I think I need to switch to the more powerful JMockit to achieve my test.

However, if I use the @Testedannotation on the Session class, the init method called from the constructor throws an Exception.

If I use @Mocked on the Session class I don't have the real methods available to test.

What is not shown below is that I will also need access to the details object during the test. However, this is retrieved from the plugin object during the constructor and the constructor calls the problematic init method.

How do I test the checkDetails method with JMockit?

Test Code

import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.Test;

public class SessionTest {

    @Test
    public void testSession() {
        Session session = Mockito.mock(Session.class);
        Mockito.doCallRealMethod().when(session).checkDetails(Mockito.any(String.class));

        Assert.assertEquals(session.checkDetails("Foo"), true);
    }

}

Class to test

import java.net.Socket;
import java.net.SocketException;
import java.util.List;

public class Session {
    private final Socket socket;
    private final List<String> details;
    private final Plugin plugin;

    public Session(Socket socket, Plugin plugin) throws SocketException {
        this.socket = socket;
        this.plugin = plugin;
        this.details = plugin.getDetails();
        init();
    }

    public void init() throws SocketException {
        socket.setTcpNoDelay(true);
    }

    public boolean checkDetails(String item) {
        for (String detail : details) {
            if (detail.equals(item)) {
                return true;
            }
        }
        return false;
    }
}

Dependency

import java.util.List;

public class Plugin {
    private List<String> details;

    public List<String> getDetails() {
        return details;
    }
}

Note: This is a very much simplified version of the code and changing the production code is not possible at this time.

2

There are 2 answers

2
dcsohl On

I would not run this test with partial mocking, personally. This is not a case where you would want to partially mock your code-under-test. (I only mock the CUT in cases where I have a utility method I am independently testing, and I want to assume that utility method works just fine, and test the larger context. You are not doing that here.)

I would take one of two tacks, depending on exactly what my goals were.

Option One

Treat the constructor as part of what you are testing:

@RunWith(JMockit.class)
public class SessionTest {

    @Injectable Socket socket;
    @Injectable Plugin plugin;

    @Test
    public void testCheckDetails() throws Exception {
        new Expectations() {{
            plugin.getDetails(); result = Arrays.asList("Foo", "Bar", "Baz");
        }};

        Session session = new Session(socket, plugin);


        boolean b = session.checkDetails("Foo");
        assertThat(b, is(true));

    }
}

Option Two

Assume the object was constructed properly and has its List<String> details field initialized properly.

@RunWith(JMockit.class)
public class SessionTest {

    @Tested
    Session cut;

    @Injectable Socket socket;
    @Injectable Plugin plugin;

    @Test
    public void testCheckDetails() throws Exception {
        List<String> details = Arrays.asList("Foo", "Bar", "Baz");
        Deencapsulation.setField(cut, details);

        boolean b = cut.checkDetails("Foo");
        assertThat(b, is(true));
    }
}

Actual Partial Mocking

If this doesn't cut it and you really have your heart set on partial mocking (e.g. you haven't given the full story and you really need to do it), here's how it would work. Let's suppose we have one more method on Session:

public List<String> getDetails() {
    return plugin.getDetails();
}

and let's further assume that checkDetails consults this helper method rather than a field in your instance.

public boolean checkDetails(String item) {
    for (String detail : getDetails()) {
        if (detail.equals(item)) {
            return true;
        }
    }
}

Then a test using partial mocking might look like this:

@RunWith(JMockit.class)
public class SessionTest {

    @Tested
    Session cut;

    @Injectable Socket socket;
    @Injectable Plugin plugin;

    @Test
    public void testCheckDetails() throws Exception {
        new Expectations(cut) {
            {
                cut.getDetails(); result=Arrays.asList("Foo", "Bar", "Baz");
            }
        };

        boolean b = cut.checkDetails("Foo");
        assertThat(b, is(true));
    }
}

Faking It

Alternatively, you can use a fake. Again, same assumptions of a getDetails() method as in the last section:

@RunWith(JMockit.class)
public class SessionTest {

    @Tested
    Session cut;

    @Injectable Socket socket;
    @Injectable Plugin plugin;

    @Test
    public void testCheckDetails() throws Exception {
        new MockUp<Session>() {
            @Mock
            List<String> getDetails() {
                return Arrays.asList("Foo", "Bar", "Baz");
            }
        };

        Session cut = new Session(socket, plugin);

        boolean b = cut.checkDetails("Foo");
        assertThat(b, is(true));
    }
}

Final Words

Note that in all cases, when you start to partially mock or fake an object, then the default action is to do the real implementation for that partial mock or fake. Only methods explicitly mocked/faked will be, well, mocked or faked. This is in contrast to the Mockito method you use where there's an explicit doCallRealMethod() (and you would be calling a mocked method without it).

Also, the assertThat(...) stuff is not part of JMockit; it's part of JUnit and is just how I like to do my tests, and I'm too lazy to go back and change it to the assertions you had.

0
opticyclic On

The JMockit equivalent to Session session = Mockito.mock(Session.class); is either

Session session = new MockUp<Session>() {}.getMockInstance();

or

@Mock
Session session;

As mentioned in answer by @dcsohl there isn't a direct equivalent of Mockito.doCallRealMethod().when(session).checkDetails(Mockito.any(String.class));

However, you can use the MockUp approach to use all real methods and then specify a method to exclude. In this case the init method was the main reason for mocking and what we needed to exclude from being called.

new MockUp<Session>() {
    @Mock
    public void init() {
        //Don't call the real init
    }
};

session = new Session(mockPlugin, mockSocket);

Note that in this case we don't call getMockInstance on the MockUp. However, JMockit will create the Mock when we all the contstructor in the next line.