简体   繁体   English

Mockito 单元测试 RestTemplate

[英]Mockito unit testing RestTemplate

I am using RestTemplate postForEntity method to post body to an endpoint.我正在使用 RestTemplate postForEntity方法将正文发布到端点。 I need help with writing test case for my code using Mockito.我需要帮助使用 Mockito 为我的代码编写测试用例。 The return type is void but it can be changed to Types or code if needed to test.返回类型为 void 但如果需要测试,可以更改为Typescode I have referred many other documentation but they are very general, I tried using them but most did not work for me as the request and return type are different.我参考了许多其他文档,但它们非常通用,我尝试使用它们但大多数对我不起作用,因为request和返回类型不同。 . . Any suggestions are appreciated.任何建议表示赞赏。 Thank you谢谢

Here is my Java class这是我的 Java 类

    public void postJson(Set<Type> Types){
        try {
            String oneString = String.join(",", Types);
           Map<String, String> requestBody = new HashMap<>();
            requestBody.put("type", oneString);
            JSONObject jsonObject = new JSONObject(requestBody);
            HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST,
                    new HttpEntity<>(request, getHttpHeaders()), String.class);

        } 
    }
} 

You are testing the logic inside MyClass class, so you should not mock it.您正在测试 MyClass 类中的逻辑,因此不应模拟它。 RestTemplate is a dependency inside MyClass, so this is exactly what you need to mock. RestTemplate是 MyClass 中的一个依赖项,所以这正是你需要模拟的。 In general it should look like this inside your test:一般来说,它在你的测试中应该是这样的:

This is just a simple example.这只是一个简单的例子。 A good practice would be to check that the arguments passed to your mock equal to the expected ones.一个好的做法是检查传递给模拟的参数是否等于预期的参数。 One way would be to replace Mockito.eq() with the real expected data.一种方法是用真实的预期数据替换Mockito.eq() Another is to verify it separately, like this:另一种是单独验证,像这样:

public ResponseEntity<String> postJson(Set<Type> Types){
            try {
                String oneString = String.join(",", Types);
               Map<String, String> requestBody = new HashMap<>();
                requestBody.put("type", oneString);
                JSONObject jsonObject = new JSONObject(requestBody);
                HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
    ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST,
                        new HttpEntity<>(request, getHttpHeaders()), String.class);
                } 
        }
        return Types;

You can write test for above method as follows您可以为上述方法编写测试如下

   @Mock
   RestTemplate restTemplate;

   private Poster poster;
   
   HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), getHttpHeaders());
   
   ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.POST, request, String.class);
   
   Mockito.verify(restTemplate, Mockito.times(1)).exchange(
       Mockito.eq(uri),
       Mockito.eq(HttpMethod.POST),
       Mockito.eq(request),
       Mockito.eq(String.class));
   
   Assert.assertEquals(result, poster.postJson(mockData));
   
   HttpHeaders getHttpHeaders() {
       HttpHeaders headers = new HttpHeaders();
       headers.add(// whatever you need to add);
       return headers;
   }

A while back, I wrote about unit testing and test doubles .不久前,我写了关于单元测试和测试替身的文章 You can have a read as a starting point on how to approach unit testing.您可以将阅读作为如何进行单元测试的起点。

Some of it's key take aways are:其中一些关键要点是:

  • Test Behaviour not Implementation.测试行为而不是实现。 Tests that are independent of implementation details are easier to maintain.独立于实现细节的测试更容易维护。 In most cases, tests should focus on testing your code's public API, and your code's implementation details shouldn't need to be exposed to tests.在大多数情况下,测试应该专注于测试代码的公共 API,并且代码的实现细节不需要暴露给测试。
  • The size of the unit under test is discretionary but the smaller the unit, the better it is.被测单元的大小是任意的,但单元越小越好。
  • When talking about unit tests, a more quintessential distinction is whether the unit under test should be sociable or solitary.在谈论单元测试时,一个更典型的区别是被测单元应该是社交的还是单独的。
  • A test double is an object that can stand in for a real object in a test, similar to how a stunt double stands in for an actor in a movie.测试替身是一种可以在测试中代替真实物体的对象,类似于特技替身在电影中代表演员的方式。 They are test doubles not mocks.他们是测试替身而不是嘲笑。 A mock is one of the test doubles.模拟是测试替身之一。 Different test doubles have different uses.不同的测试替身有不同的用途。

It's hard to write a whole test as a lot of information is missing.由于缺少大量信息,因此很难编写完整的测试。 Eg what Type is.例如什么Type是。 As you did not posted the name of your class I'm just name it MyClass for now.由于您没有发布您班级的名称,因此我现在将其命名为MyClass Also I'm assuming that the RestTemplate is injected via the constructor like另外我假设 RestTemplate 是通过构造函数注入的

MyClass(RestTemplate restTemplate) {
    this.restTemplate = restTemplate;
}

Following is a draft for a unit test using JUnit 5 and Mockito .以下是使用JUnit 5Mockito的单元测试草案。 I would suggest mocking the RestTemplate .我建议嘲笑RestTemplate I have to admit that this way we will not cover to test the usage of MappingJackson2HttpMessageConverter and StringHttpMessageConverter in our test.不得不承认,这种方式我们不会覆盖测试MappingJackson2HttpMessageConverterStringHttpMessageConverter在我们的测试中的使用。

So a very raw draft could look like this所以一个非常原始的草稿可能看起来像这样

import java.util.ArrayList;
import java.util.Set;

import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.http.HttpEntity;
import org.springframework.web.client.RestTemplate;

class MyClassTest {

    private RestTemplate restTemplate;
    private MyClass myClass;

    @BeforeEach
    void setUp() {
        restTemplate = Mockito.mock(RestTemplate.class);
        myClass = new MyClass(restTemplate);
    }

    @Test
    void callMethod() {
        Set<Type> types = Set.of(/* one of your Types */);
        String url = "http://someUrl";
        String httpResult = "";

        Mockito.when(restTemplate.getMessageConverters()).thenReturn(new ArrayList<>());

        ArgumentCaptor<HttpEntity> request = ArgumentCaptor.forClass(HttpEntity.class);
        Mockito.when(restTemplate.postForObject(url, request.capture(), String.class)).thenReturn(httpResult);

        myClass.callMethod(types, url);

        HttpEntity<String> actualHttpEntity = request.getValue();
        Assert.assertEquals(actualHttpEntity.getBody(), "");
    }
}

Here is my solution to this problem, but it requires some changes.这是我对这个问题的解决方案,但它需要一些更改。

First of all, you have to externalize the RestTemplate as a bean and add converters in its initialization.首先,您必须将RestTemplate外部RestTemplate bean 并在其初始化中添加转换器。 This way you will get rid of not covering those converters.通过这种方式,您将摆脱不覆盖这些转换器的情况。

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
    restTemplate.getMessageConverters().add(jsonConverter);
    StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
    restTemplate.getMessageConverters().add(stringConverter);
    return restTemplate;
}

Here's the new class that contains postJson method.这是包含postJson方法的新类。 As you can see, the url and restTemplate are injected through the constructor.如您所见, urlrestTemplate是通过构造函数注入的。 This way we can test different cases.这样我们就可以测试不同的情况。

public class Poster {

    private RestTemplate restTemplate;
    private String url;

    public Poster(RestTemplate restTemplate, String url) {
        this.restTemplate = restTemplate;
        this.url = url;
    }

    public void postJson(Set<Type> types) {
        try {
            String oneString = types.stream().map(Type::toString).collect(Collectors.joining(","));
            Map<String, String> requestBody = new HashMap<>();
            requestBody.put("type", oneString);
            requestBody.put("data", "aws");
            JSONObject jsonObject = new JSONObject(requestBody);
            HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), null);

            ResponseEntity<String> result = restTemplate
                    .postForEntity(url, new HttpEntity<>(request, getHttpHeaders()), String.class);

            int code = result.getStatusCodeValue();
        } catch (Exception ignored) {}
    }

    private HttpHeaders getHttpHeaders() {
        return new HttpHeaders();
    }

}

And here's the test class for that method.这是该方法的测试类。

class PosterTest {

    @Mock
    private RestTemplate restTemplate;

    private String url;
    private Poster poster;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        this.url = "http://example.com/posturl";
        this.poster = new Poster(restTemplate, url);
    }

    @Test
    void postJson() {
        // set input, I used TreeSet just to have a sorted set
        // so that I can check the results against
        Set<Type> types = new TreeSet<>(Comparator.comparing(Type::toString));
        types.add(new Type("a"));
        types.add(new Type("b"));
        types.add(new Type("c"));
        types.add(new Type("d"));
        // response entity
        ResponseEntity<String> response = ResponseEntity.ok("RESPONSE");

        // mockito mock
        Mockito.when(restTemplate.postForEntity(
                ArgumentMatchers.eq(url),
                ArgumentMatchers.any(HttpHeaders.class),
                ArgumentMatchers.eq(String.class)
        )).thenReturn(response);

        // to check the results
        Map<String, String> requestBody = new HashMap<>();
        requestBody.put("type", "a,b,c,d");
        requestBody.put("data", "aws");
        JSONObject jsonObject = new JSONObject(requestBody);
        HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), null);
        HttpEntity<HttpEntity<String>> httpEntity = new HttpEntity<>(request, new HttpHeaders());

        // actual call
        poster.postJson(types);

        // verification
        Mockito.verify(restTemplate, times(1)).postForEntity(
                ArgumentMatchers.eq(url),
                ArgumentMatchers.eq(httpEntity),
                ArgumentMatchers.eq(String.class));
    }
}

It is better to have the dependencies like RestTemplate or other ServiceClass es so that they can be mocked and tested easily.最好有RestTemplate或其他ServiceClass类的依赖RestTemplate ,以便可以轻松RestTemplate和测试它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM