尝试在模拟的Eclipse Paho MqttClient

时间:2018-05-22 12:42:05

标签: java unit-testing mockito powermock paho

我有以下,大大简化的课程:

public class MyClass
{
    private MqttClient field;

    public void test() throws MqttException
    {
        field = new MqttClient(null, null);
        field.connect();
    }
}

我正在使用Eclipse Paho's MqttClient课程。

我想用UnitTest测试test方法。为了控制MqttClient实例,我想模拟对象。我的测试类看起来如下:

public class TestMyClass
{
    // Make sure we are running with the PowerMockAgent initialized
    static
    {
        PowerMockAgent.initializeIfNeeded();
    }

    // Make sure we are using PowerMock
    @Rule
    public PowerMockRule    rule    = new PowerMockRule();

    // Mocked MqttClient
    @Mock
    MqttClient              field;

    // MyClass-Object injected with mocks.
    @InjectMocks
    MyClass                 myClass = new MyClass();

    @PrepareForTest(MyClass.class)
    @Test
    public void test() throws Exception
    {
        // Initialize our mocks from annotations
        MockitoAnnotations.initMocks(this);

        // Catch the "new MqttClient(null, null);"
        PowerMockito.whenNew(MqttClient.class).withAnyArguments().thenReturn(field);


        myClass.test();

    }
}

当我执行测试时,我遇到以下错误:

java.lang.NullPointerException
    at test.java.test.MyClass.test(MyClass.java:13)
    at test.java.test.TestMyClass.test(TestMyClass.java:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.powermock.modules.junit4.rule.PowerMockStatement.evaluate(PowerMockRule.java:73)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

我可以在field上调用任何方法,它始终是空引用。我的问题是我不知道为什么会这样。

我将field的类型更改为其他几个类,包括java.awt.pointSerializationInstantiatorHelper(以便测试它是否与MqttClient有关一个外部依赖项)以及我项目中的几个类,在这些对象上调用适当的方法。在所有这些情况下,问题都不存在,测试成功没有任何问题。模拟对象上的方法被调用,什么都不做 - 正如我想要的那样。

仅在使用MqttClient类时才会出现此问题。或者至少我没有找到任何其他类来重现这个问题。

我诚实地完全失去了这一点。是什么让MqttClient课特别?为什么我看起来不像任何其他类一样嘲笑它?我错过了什么?

//修改

我通过创建MqttClient的本地版本来缩小问题范围,复制源代码。老实说,我现在更加困惑。该类有三个具有以下签名的构造函数:

1)

public MqttClient(  String serverURI,
                    String clientId) throws MqttException

2)。

public MqttClient(  String serverURI,
                    String clientId,
                    MqttClientPersistence persistence) throws MqttException

3。)

public MqttClient(  String serverURI,
                    String clientId,
                    MqttClientPersistence persistence,
                    ScheduledExecutorService executorService) throws MqttException

如果删除了这三个构造函数中的一个,那么一切正常。 该类与这三个构造函数中的两个的任何组合都可以正常工作。只要存在所有三个构造函数,就会出现上述错误消息。 这导致我假设它可能与PowerMockito没有找到正确的构造函数有关?如果是这样,那怎么可以避免呢?

2 个答案:

答案 0 :(得分:1)

被模拟的目标似乎没有正确配置,因此在进行测试时表现不正常。

有时候保持简单(KISS)是必经之路。

@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)
public class TestMyClass {

    @Test
    public void test() throws Exception {
        //Arrange

        // mock MqttClient
        MqttClient field = mock(MqttClient.class);
        // setup the "new MqttClient(null, null);"
        PowerMockito.whenNew(MqttClient.class).withArguments(isNull(), isNull()).thenReturn(field);

        MyClass subject = new MyClass();

        //Act
        subject.test();

        //Assert
        PowerMockito.verifyNew(MqttClient.class).withArguments(isNull(), isNull());
        verify(field).connect();
    }
}

通过将.withArguments与适当的参数或匹配器配合使用,它可以帮助专门缩小要测试的模拟构造函数的范围,避免在存在多个构造函数时发生任何冲突。

基于示例中所示的简化的测试类,在本地初始化依赖项时(即紧密耦合),没有注入依赖项,因此实际上不需要将模拟注入到测试类中。

此外,PrepareForTest通常是在测试类上完成的,而不是在这种情况下的测试方法,如documentation所述

  

所有用法都需要@RunWith(PowerMockRunner.class)@PrepareForTest 在课程级别上标注

强调我的

答案 1 :(得分:1)

我认为您遇到的麻烦与本期报道的

Mocking whenNew using withAnyArguments does not match null arguments #891

问题在于是否声明了多个构造函数:

  

第一个构造函数的匹配器正确生成(使用形式or(isA(Class),isNull()))。但是其余的构造函数却没有(它们只有any(Class)导致它们与空值不匹配)。

因此,在解决问题之前,您可以使用以下解决方法:

MqttClient field = mock(MqttClient.class);

代替@Mock MqttClient field;