【问题标题】:mockito and jetty-client: receiving NullPointerException when start() and stop() methods are invokedmockito 和 jetty-client:在调用 start() 和 stop() 方法时接收 NullPointerException
【发布时间】:2014-08-05 22:52:44
【问题描述】:

我正在测试这个 sn-p 的代码

  this.httpClient.start();
  ContentResponse response = this.httpClient.newRequest(Protocol.IMAGES())
                                            .method(HttpMethod.GET)
                                            .send();

  if(response.getStatus() != 200)
    throw new HostIsNotReachableException();

  images = ImageList.fromJson(response.getContentAsString());

  this.httpClient.stop();

使用

HttpClient httpClient = mock(HttpClient.class);
Request request = mock(Request.class);
ContentResponse response = mock(ContentResponse.class);
when(httpClient.newRequest(Protocol.IMAGES())).thenReturn(request);
when(request.method(HttpMethod.GET)).thenReturn(request);
when(request.send()).thenReturn(response);
when(response.getStatus()).thenReturn(200);
when(response.getContentAsString()).thenReturn(gson.toJson(images));
...
verify(httpClient, times(1)).start();
verify(httpClient, times(1)).stop();
verify(httpClient, times(1)).newRequest(Protocol.IMAGES());
verify(request, times(1)).method(HttpMethod.GET);
verify(request, times(1)).send();

当调用 start() 和/或 stop() 方法时,测试会报告意外的 NullPointerException。模拟定义中有什么我想念的吗?

这是异常的日志:

java.lang.NullPointerException
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:61)
at com.meetecho.docker.client.ClientTest.itShouldGetTheImagesList(ClientTest.java:99)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
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.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
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.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:86)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:69)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:105)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:355)
at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)

【问题讨论】:

  • 请发布 NPE 的堆栈跟踪。

标签: java testing mocking jetty mockito


【解决方案1】:

很好的问题,我遇到了一个非常相似的问题,这个 stackoverflow 出现在我的谷歌搜索结果的顶部。

在我的例子中,我试图测试一个将我的自定义过滤器添加到我的 ApplicationContext 的代码 sn-p,并在必要时模拟出对象。我遇到的问题是,当我的代码调用 filterHolder.start()(实际上是 AbstractLifeCycle 中的最终方法 start())时,它会抛出 NullPointerException。

进一步构建您自己的答案:您可以使用 Powermock 来克服 Mockito 无法模拟最终方法的问题。

使用项目中包含的 Powermock 库,启用 Powermock 运行器并标记 AbstractLifeCycle.class 类以进行字节码操作:

@PrepareForTest(AbstractLifeCycle.class)
@RunWith(PowerMockRunner.class)
public class MyTestClass extends Assert {

然后,模拟出 initialize() 和 start() 方法:

doAnswer(new DoesNothing()).when(holder).initialize();
doAnswer(new DoesNothing()).when(holder).start();

它应该可以正常工作。

【讨论】:

  • 感谢您的回复。现在我的问题是:为什么 mokito 不实现静态模拟?
【解决方案2】:

我会自动回复我的答案,因为没有人提出正确的解决方案。

我意识到 jetty-client 类的启动/停止方法是最终的。 Mockito 不模拟最终方法,而是执行它们的实际实现。

我现在是如何解决这个问题的?通过开始使用 jersey-client。

【讨论】:

    【解决方案3】:

    看起来对象 httpClient 为空。您实际上是在模拟 httpClient 但在您正在测试的实际代码中从未收到模拟。

    即使您注入对象或在测试代码中使用新的 HttpClient 实例化它,您也必须添加一个 setter 方法才能从外部设置 httpClient 对象。当然,这种方法仅用于测试目的,但它是一种常见的做法。

    也就是说,假设你的代码在A类中,并且包含要测试的代码的方法是methodA,你应该有类似的东西:

    class A {
        private HttpClient httpClient=null; 
    
        public methodA {
            if (this.httpClient==null) this.httpClient = new HttpClient(...);
            <YOUR CODE>
        }
    
        public void setHttpClient(HttpClient httpClient) {
            this.httpClient = httpClient;
        }
        ...
     }
    

    在测试中,您应该调用方法 setHttpClient 作为参数传递您在第一行创建的 HttpClient 的模拟。

    【讨论】:

    • 我使用Client uut = new Client(); Field f = Client.class.getDeclaredField("httpClient"); f.setAccessible(true); f.set(uut, httpClient);设置了httpClient对象,为简洁起见,我在上面的代码中省略了它。
    • 好!您仍然可以采用相同的方法。如果 httpClient 与 null 不同,请确保不要执行代码,因为如果是这种情况,则意味着您已经从单元测试中手动设置了 httpClient。因此,基本上,如果您将 asnwer 中提供的代码 this.httpClient = new HttpClient(...); 替换为您刚刚输入的代码,它应该可以正常工作。
    • 考虑到您有一个成员变量this.httpClient,您可以通过注入new HttpClient() 作为构造函数参数来消除对反射的需求。它明确了您的班级需要什么,并且您的测试更干净。如果您坚持使用 HttpClient 参考,您可能还需要考虑 multi-threading issues
    【解决方案4】:

    在较新的 Mockito 版本中,您可以按照 https://www.baeldung.com/mockito-final 中的建议执行以下操作:

    在 Mockito 可用于模拟 final 类和方法之前,它 需要配置。

    我们需要添加一个文本文件到项目的 src/test/resources/mockito-extensions 目录名为 org.mockito.plugins.MockMaker 并添加一行文本:

    模拟制造者内联

    Mockito 检查扩展目录 加载时的配置文件。此文件启用模拟 最终方法和类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-08
      • 1970-01-01
      • 1970-01-01
      • 2019-08-27
      • 1970-01-01
      • 2016-01-12
      • 1970-01-01
      相关资源
      最近更新 更多