如何将部分模拟从Mockito转换为JMockit?

时间:2016-12-15 04:32:22

标签: mockito jmockit

我有一些使用Mockito的测试代码(见下文),但是,我认为我需要切换到更强大的JMockit来实现我的测试。

但是,如果我在Session类上使用@Tested注释,则从构造函数调用的init方法会抛出异常。

如果我在Session类上使用@Mocked,我就没有可用于测试的真实方法。

下面未显示的是我还需要在测试期间访问details对象。但是,这在构造函数期间从plugin对象检索,构造函数调用有问题的init方法。

如何使用JMockit测试checkDetails方法?

测试代码

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

}

要测试的课程

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

依赖

import java.util.List;

public class Plugin {
    private List<String> details;

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

注意:这是一个非常简化的代码版本,目前无法更改生产代码。

2 个答案:

答案 0 :(得分:0)

我不会亲自用部分嘲笑来进行这个测试。这不是您想要部分模拟测试中的代码的情况。 (我只是在我有一个实用工具方法的情况下模拟CUT我独立测试,我想假设实用方法工作得很好,并测试更大的上下文。你不是在这里做的。)

我会采取两种方法中的一种,具体取决于我的目标。

选项一

将构造函数视为您正在测试的内容的一部分:

@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));

    }
}

选项二

假设对象构造正确并且正确初始化了List<String> details字段。

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

实际部分模拟

如果这没有削减它,你真的有你的心脏部分嘲笑(例如你没有完整的故事,你真的需要这样做),这是它的工作方式。假设我们在Session上还有一个方法:

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

让我们进一步假设checkDetails参考这个辅助方法,而不是实例中的字段。

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

然后使用部分模拟的测试可能如下所示:

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

假装

或者,您可以使用假货。同样,getDetails()方法的假设与上一节相同:

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

最后的话

请注意,在所有情况下,当您开始部分模拟或伪造对象时,默认操作是为该部分模拟或伪造做真正的实现。只有明确模拟/伪造的方法才会被嘲笑或伪造。这与您使用的Mockito方法形成对比,其中有明确的doCallRealMethod()(如果没有它,您将调用模拟方法)。

此外,assertThat(...)内容不是JMockit的一部分;它是JUnit的一部分,就是我喜欢做我的测试的方式,而且我懒得回去把它改成你所拥有的断言。

答案 1 :(得分:0)

等同于Session session = Mockito.mock(Session.class);的JMockit是

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

@Mock
Session session;

正如@dcsohl在回答中提到的,没有直接等同于Mockito.doCallRealMethod().when(session).checkDetails(Mockito.any(String.class));

但是,您可以使用MockUp方法来使用所有实际方法,然后指定要排除的方法。在这种情况下,init方法是模拟的主要原因以及我们需要排除被调用的内容。

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

session = new Session(mockPlugin, mockSocket);

请注意,在这种情况下,我们不会在MockUp上调用getMockInstance。但是,当我们在下一行中的所有contstructor时,JMockit将创建Mock。