首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用Mockito模拟Apache后HTTPClient

使用Mockito模拟Apache后HTTPClient
EN

Stack Overflow用户
提问于 2022-06-16 17:12:43
回答 4查看 496关注 0票数 1

我正在尝试使用Mockito和本文来测试HttpClient。我收到下面的错误,不知道如何修复。错误在下面。我也非常关注文章的内容。它在CloseableHttpResponse closeableHttpResponse = client.execute(httpPost)上失败了,而我已经在嘲笑它了。

资源:使用Mockito模拟Apache HTTPClient

主要代码:

代码语言:javascript
运行
复制
public class ProductService {

    private final VaultConfig vaultConfig;
    private final AppConfig appConfig;

    public ProductService(VaultConfig vaultConfig,
                          @Autowired AppConfig appConfig) {
            this.vaultConfig = vaultConfig;
            this.appConfig = appConfig;
    }

    private void createAccessToken() {
        String httpUrl = MessageFormat.format("{0}/api/v1/authentication/login",
            appConfig.getProductServerUrl());
        CloseableHttpClient client = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(httpUrl);
        List<NameValuePair> httpParams = new ArrayList<NameValuePair>();
        httpParams.add(new BasicNameValuePair("username", this.vaultConfig.getProductAdminUsername()));
        httpParams.add(new BasicNameValuePair("password", this.vaultConfig.getProductAdminPassword()));

        try {
            httpPost.setEntity(new UrlEncodedFormEntity(httpParams));
            CloseableHttpResponse closeableHttpResponse = client.execute(httpPost);
            HttpEntity entity = closeableHttpResponse.getEntity();
            String tokenDataJson = EntityUtils.toString(entity, "UTF-8");
            String newAccessToken = new Gson().fromJson(tokenDataJson, Map.class).get("access_token").toString();
            this.vaultConfig.setProductAccessToken(newAccessToken);
        } catch (Exception e) {
            logger.error("Unable to create access token: " + e.getMessage());
        }
    }

测试尝试:

代码语言:javascript
运行
复制
public class ProductServiceTest {

    private ProductService productService;
    @Mock
    HttpClients httpClients;
    @Mock
    CloseableHttpClient closeableHttpClient;
    @Mock
    HttpPost httpPost;
    @Mock
    CloseableHttpResponse closeableHttpResponse;
    @Mock
    private VaultConfig vaultConfig;
    @Mock
    private AppConfig appConfig;

    @BeforeEach
    public void initialize() {
        MockitoAnnotations.openMocks(this);
        productService = new ProductService(vaultConfig, appConfig);
    }


   void getAccessTokenWhenEmpty() throws IOException {
        //given
        String expectedProductAccessToken = "ABC";

        //and:
        given(appConfig.getProductServerUrl()).willReturn("https://test.abcd.com");
        given(closeableHttpClient.execute(httpPost)).willReturn(closeableHttpResponse);
        given(vaultConfig.getProductAccessToken()).willReturn("");

        //when
        String actualProductAccessToken = ProductService.getAccessToken();

        //then
        Assertions.assertEquals(actualProductAccessToken,expectedProductAccessToken);
    }

错误:

代码语言:javascript
运行
复制
    } catch (Exception e) {

java.net.UnknownHostException: test.abcd.com: unknown error
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2022-06-25 09:50:48

一个更简单、更干净和不那么脆弱的方法就是不要嘲笑HttpClient。它只会导致您的测试代码具有模式“模拟返回模拟和返回的模拟返回另一个模拟,反过来返回另一个模拟返回.”它看上去很丑,对我来说是一种暗号。

相反,使用一个真实的HTTPClient实例并使用诸如WireMockMockWebServer之类的工具来模拟外部API。

我最喜欢的是MockWebServer,你可以这样做:

代码语言:javascript
运行
复制
public class ProductServiceTest {
    
    
    private ProductService productService;

    @Mock
    private AppConfig appConfig;

    private MockWebServer server;

    @Test
    public void getAccessTokenWhenEmpty(){


        server.start();
        HttpUrl baseUrl = server.url("/api/v1/authentication/login");

        given(appConfig.getProductServerUrl()).willReturn("http://" + baseUrl.host() +":" + baseUrl.port());
        productService = new ProductService(vaultConfig, appConfig);
        
        //stub the server to return the access token response
        server.enqueue(new MockResponse().setBody("{\"access_token\":\"ABC\"}"));

       //continues execute your test

    }


}
票数 1
EN

Stack Overflow用户

发布于 2022-06-16 19:25:48

创建的模拟没有在ProductService中使用,因为它们没有在构造中传递。类依赖项应该通过构造注入,因为它们是强制性的。

如果您像下面这样更改了实现,这将有所帮助。密码不完整。

代码语言:javascript
运行
复制
public class ProductService {

    private final VaultConfig vaultConfig;
    private final AppConfig appConfig;
    private final CloseableHttpClient client;

    public ProductService(VaultConfig vaultConfig,
                          @Autowired AppConfig appConfig, CloseableHttpClient client) {
            this.vaultConfig = vaultConfig;
            this.appConfig = appConfig;
    }

    private void createAccessToken() {
        String httpUrl = MessageFormat.format("{0}/api/v1/authentication/login",
            appConfig.getProductServerUrl());
        HttpPost httpPost = new HttpPost(httpUrl);
        List<NameValuePair> httpParams = new ArrayList<NameValuePair>();
        httpParams.add(new BasicNameValuePair("username", this.vaultConfig.getProductAdminUsername()));
        httpParams.add(new BasicNameValuePair("password", this.vaultConfig.getProductAdminPassword()));

        try {
            httpPost.setEntity(new UrlEncodedFormEntity(httpParams));
            CloseableHttpResponse closeableHttpResponse = client.execute(httpPost);
            HttpEntity entity = closeableHttpResponse.getEntity();
            String tokenDataJson = EntityUtils.toString(entity, "UTF-8");
            String newAccessToken = new Gson().fromJson(tokenDataJson, Map.class).get("access_token").toString();
            this.vaultConfig.setProductAccessToken(newAccessToken);
        } catch (Exception e) {
            logger.error("Unable to create access token: " + e.getMessage());
        }
    }
代码语言:javascript
运行
复制
public class ProductServiceTest {

    @InjectMocks
    private ProductService productService;
    @Mock
    CloseableHttpClient closeableHttpClient;
    @Mock
    private VaultConfig vaultConfig;
    @Mock
    private AppConfig appConfig;
    @Mock
    CloseableHttpResponse closeableHttpResponse;
    @Mock
    HttpEntity entity

    @BeforeEach
    public void initialize() {
        MockitoAnnotations.openMocks(this);
    }


   void getAccessTokenWhenEmpty() throws IOException {
        //given
        String expectedProductAccessToken = "ABC";

        //and:
        given(appConfig.getProductServerUrl()).willReturn("https://test.abcd.com");
        given(closeableHttpClient.execute(httpPost)).willReturn(closeableHttpResponse);
        given(vaultConfig.getProductAccessToken()).willReturn("");

        // my additional code
        given(closeableHttpResponse.getEntity()).willReturn(entity);
        given(entity.....).willReturn(....)   // mock the entity for the EntityUtils.toString(entity, "UTF-8");

        //when
        String actualProductAccessToken = ProductService.getAccessToken();

        //then
        Assertions.assertEquals(actualProductAccessToken,expectedProductAccessToken);
    }
票数 2
EN

Stack Overflow用户

发布于 2022-06-23 22:25:47

如果您不想更改实现,也不希望添加构造函数或创建CloseableHttpClient的附加方法。

您需要模拟静态方法HttpClients.createDefault()

解决方案1: Mockito模拟静态方法

自从3.4.0版本以来,Mockito支持模拟静态方法。

代码语言:javascript
运行
复制
        try (MockedStatic mocked = mockStatic(Foo.class)) {
            mocked.when(Foo::method).thenReturn("bar");
            assertEquals("bar", Foo.method());
            mocked.verify(Foo::method);
        }

首先,您需要将mockito内嵌添加到项目中

代码语言:javascript
运行
复制
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>4.6.1</version>
    <scope>test</scope>
</dependency>

执行情况:

代码语言:javascript
运行
复制
public class ProductServiceTest {

    private ProductService productService;

    MockedStatic<HttpClients> httpClientsStaticMock;

    @Mock
    CloseableHttpClient closeableHttpClient;
    @Mock
    HttpPost httpPost;
    @Mock
    CloseableHttpResponse closeableHttpResponse;
    @Mock
    private VaultConfig vaultConfig;
    @Mock
    private AppConfig appConfig;

    @BeforeEach
    public void initialize() {
        MockitoAnnotations.openMocks(this);
        productService = new ProductService(vaultConfig, appConfig);
        //create static Mock
        httpClientsStaticMock = mockStatic(HttpClients.class);
        //when HttpClients.createDefault() called return mock of CloseableHttpClient
        httpClientsStaticMock.when(HttpClients::createDefault).thenReturn(closeableHttpClient);
    }

    void getAccessTokenWhenEmpty() throws IOException {
        //given
        String expectedProductAccessToken = "ABC";

        //and:
        given(appConfig.getProductServerUrl()).willReturn("https://test.abcd.com");
        given(closeableHttpClient.execute(httpPost)).willReturn(closeableHttpResponse);
        given(vaultConfig.getProductAccessToken()).willReturn("");

        //when
        String actualProductAccessToken = ProductService.getAccessToken();

        //then
        Assertions.assertEquals(actualProductAccessToken, expectedProductAccessToken);
    }

    @AfterEach
    public void afterTest() {
        //close static Mock
        httpClientsStaticMock.close();
    }
}

解决方案2: 基于PowerMock的模拟静态方法

依赖关系:

代码语言:javascript
运行
复制
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>

执行情况:

代码语言:javascript
运行
复制
@RunWith(PowerMockRunner.class)
@PrepareForTest({ ProductService.class, HttpClients.class})
public class ProductServiceTest {

    private ProductService productService;

    @Mock
    CloseableHttpClient closeableHttpClient;
    @Mock
    HttpPost httpPost;
    @Mock
    CloseableHttpResponse closeableHttpResponse;
    @Mock
    private VaultConfig vaultConfig;
    @Mock
    private AppConfig appConfig;

    @BeforeEach
    public void initialize() {
        MockitoAnnotations.openMocks(this);
        productService = new ProductService(vaultConfig, appConfig);
        //create static Mock
        PowerMockito.mockStatic(HttpClients.class);
        //when HttpClients.createDefault() called return mock of CloseableHttpClient
        when(HttpClients.createDefault()).thenReturn(closeableHttpClient);
    }

    void getAccessTokenWhenEmpty() throws IOException {
        //given
        String expectedProductAccessToken = "ABC";

        //and:
        given(appConfig.getProductServerUrl()).willReturn("https://test.abcd.com");
        given(closeableHttpClient.execute(httpPost)).willReturn(closeableHttpResponse);
        given(vaultConfig.getProductAccessToken()).willReturn("");

        //when
        String actualProductAccessToken = ProductService.getAccessToken();

        //then
        Assertions.assertEquals(actualProductAccessToken, expectedProductAccessToken);
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72649574

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档