代码之家  ›  专栏  ›  技术社区  ›  Ben

尝试在模拟Eclipse Paho MQTTClient上调用方法时发生NullPointerException

  •  1
  • Ben  · 技术社区  · 6 年前

    我有以下非常简单的课程:

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

    我正在使用 Eclipse Paho's MqttClient 班级。

    我想测试一下 test 方法。为了控制 mqtt客户端 实例我想模拟对象。我的测试课程如下:

    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 它始终是一个空引用。我的问题是我不知道为什么会这样。

    我改变了 领域 其他几个班级,包括 java.awt.point SerializationInstantiatorHelper (为了测试是否与 mqtt客户端 作为外部依赖项)以及我的项目中的几个类,对这些对象调用适当的方法。在所有这些情况下,问题不存在,测试成功,没有任何问题。模拟对象上的方法是被调用的,它不做任何事情——正如我希望它做的那样。

    仅当使用 mqtt客户端 类发生此问题。或者至少我还没有找到其他类来重现这个问题。

    老实说,我在这一点上完全迷路了。什么使 mqtt客户端 特别班?为什么我不能像其他班一样嘲笑它?我错过了什么?

    //编辑

    我通过创建本地版本的 mqtt客户端 ,在源代码上复制。老实说,我现在更困惑了。类有三个具有以下签名的构造函数:

    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 回复  |  直到 6 年前
        1
  •  1
  •   Nkosi    6 年前

    模拟目标似乎没有正确配置,因此在执行测试时,它的行为不符合预期。

    有时保持简单(接吻)是一种方法。

    @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 在类级别注释 .

    重点地雷

        2
  •  1
  •   Mạnh Quyết Nguyễn    6 年前

    我想你和本期报道的情况一样麻烦。

    Mocking whenNew using withAnyArguments does not match null arguments #891

    问题是如果声明了多个构造函数:

    第一个构造函数的匹配器是正确生成的(使用表单或(isa(class),isnull())。但其余的构造函数不是(它们只有任何(类),这会导致它们与空值不匹配)。

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

    MqttClient field = mock(MqttClient.class);
    

    而不是 @Mock MqttClient field;