【问题标题】:Issues when trying to mock Apache HTTP Client with Mockito尝试使用 Mockito 模拟 Apache HTTP 客户端时出现问题
【发布时间】:2018-01-29 11:14:01
【问题描述】:

我是 Mockito 的初学者。我有一个想要模拟的 HTTP get 调用。

需要测试的Utility文件是这个。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

public class GoogleAccessor {
private HttpClient client ;

    public void getGoogle() throws ClientProtocolException, IOException {
        String url = "http://www.google.com/search?q=httpClient";
         client = new DefaultHttpClient();
        HttpGet request = new HttpGet(url);
        try {
            HttpResponse response = client.execute(request);
            BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            StringBuffer result = new StringBuffer();
            String line = "";
            while ((line = rd.readLine()) != null) {
                result.append(line);
            }
            System.out.println(result);
        } catch (Exception e) {
            System.out.println(e);
        }
    }

}

这是我的 jUnit 测试

import static org.mockito.Mockito.when;

import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import org.mockito.Mock;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

public class TestTest {

    @Mock
    private HttpClient mClient;

    @InjectMocks
    private GoogleAccessor dummy;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testAddCustomer_returnsNewCustomer() throws Exception {
        when(mClient.execute(Mockito.any())).thenReturn(buildResponse(201, "OK", "{\"message\":\"Model was stored\"}"));
        dummy.getGoogle();
    }

    BasicHttpResponse buildResponse(int statusCode, String reason, String body) {
        BasicHttpResponse response = new BasicHttpResponse(
                new BasicStatusLine(new ProtocolVersion("HTTP", 1, 0), statusCode, reason));
        if (body != null) {
            response.setEntity(new ByteArrayEntity(body.getBytes()));
        }

        return response;
    }
}

因为,mclient 没有启动,我一直收到这个错误

java.lang.NullPointerException
    at com.amazon.cartographertests.accessors.TestTest.testAddCustomer_returnsNewCustomer(TestTest.java:34)
    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)
    ...

UPDATE1:我已从 @glytching 合并 cmets 并删除了 setup() 中的变量初始化 我已经合并了来自@Maciej Kowalski 的 cmets,并使客户端成为 Utility 类中的类变量

更新 2:

解析到正确的import -> import org.mockito.Mock后NPE错误已解决;

但是,看不到mocked response的问题依然存在

【问题讨论】:

  • @InjectMocks 是纯粹的魔法邪恶。使用良好的旧构造函数传递依赖项。

标签: java unit-testing junit mockito


【解决方案1】:

您在这里声明mClient 为模拟:

@Mock
private HttpClient mClient;

但是你也在setUp()中为它分配了一个DefaultHttpClient的具体实例:

mClient = new DefaultHttpClient();

只需从setup() 中删除该行,您的测试中使用的mClient 实例将是一个模拟。

更新:您更新后的问题显示此导入:

import org.easymock.Mock;

应该是这样的

import org.mockito.Mock;

这条指令:MockitoAnnotations.initMocks(this); 将初始化任何带有 org.mockito.Mock 注释的类成员,它不会对带有 org.easymock.Mock 注释的类成员做任何事情,所以我怀疑 mClient 没有被初始化,因此 NPE .当我通过正确的导入在本地运行您的代码时,测试成功完成,没有异常。

注意:GoogleAccessor 内部也有一个问题,您在 getGoogle 内部显式实例化HttpClient ...

HttpClient client = new DefaultHttpClient();

...所以这个方法永远不会使用模拟的HttpClient

如果你注释掉这一行:

HttpClient client = new DefaultHttpClient();

...并将这个类成员添加到GoogleAccessor:

private HttpClient client;

...然后您会发现您的模拟客户端将在getGoogle() 中使用。

用于背景;为了在类中使用模拟实例,您必须为测试运行器提供某种方式将模拟实例注入到类中。在您的测试用例中,您使用的是@InjectMocks,因此您指示测试运行器将您的模拟注入GoogleAccessor,但是由于没有该类型的类成员,因此测试运行器没有任何东西可以分配模拟到时间>。此外,即使它可以将模拟分配给某些东西,您也在声明 http 客户端 inside getGoogle() 从而强制该方法的所有调用使用 DefaultHttpClient 的具体实例。

【讨论】:

  • 如果我删除了初始化部分。我看到一个 NPE ..请查看更新后的问题
  • @AdityaReddy 我认为你输入了错误的Mock,我已经更新了解决这一点的答案。
  • 我已经更正了。这对我来说是愚蠢的。但我仍然没有看到嘲笑的回应。我看到返回的实际响应
  • 我已经更新了答案。通过这些更改,我可以确认getGoogle 的响应返回是预期的响应:{"message":"Model was stored"}
  • 声明一个构造函数 public GoogleAccessor(HttpClient client)) { ... } 并在构造时提供 HttpClient 实例,然后您可以在传递模拟实例的测试中调用它。如果你不能/不会处理依赖注入,那么你也可以声明一个公共的无参数构造函数,它委托给另一个构造函数,如下所示:this(new DefaultHttpClient());
【解决方案2】:

您正在方法本身中创建客户端。除非您创建包级别的方法,否则您无法模拟它:

HttpClient getNewClient(){
   return new DefaultHttpClient();
}

并在方法中使用它。

public void getGoogle() throws ClientProtocolException, IOException {
    String url = "http://www.google.com/search?q=httpClient";
    HttpClient client = getNewClient();

然后你需要监视被测类以保持被测方法不变并模拟getNewClient 方法以返回你想要的:

@Mock
private HttpClient mClient;

@InjectMocks
@Spy
private GoogleAccessor dummy;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
}

@Test
public void testAddCustomer_returnsNewCustomer() throws Exception {
    doReturn(null).when(mClient).execute(Mockito.any()));
    dummy.getGoogle();
}

您还需要使用doReturn().when() 语法而不是when().then(),因为您正在与间谍打交道。

【讨论】:

  • 我已将 HttpClient 设为类变量。我仍然看到这个问题。关于间谍..我确实尝试过它并看到了同样的问题。这适用于您的本地吗?
  • 您使用来自import org.easymock.Mock; 的模拟。使用 org.mockito 包
  • 我已更改,NPE 问题已解决。现在我看到返回的是实际响应而不是模拟响应
  • 它致力于将初始化也移动到类级别。感谢您对此进行调查。
猜你喜欢
  • 2014-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-07
  • 1970-01-01
  • 2022-06-10
  • 1970-01-01
相关资源
最近更新 更多