Mockito unit testing RestTemplate

醉酒当歌 提交于 2020-01-28 02:42:10

问题


I am using RestTemplate postForEntity method to post body to an endpoint. I need help with writing test case for my code using Mockito. The return type is void but it can be changed to Types or code if needed to test. 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. . Any suggestions are appreciated. Thank you

Here is my Java class

    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);

        } 
    }
} 

回答1:


You are testing the logic inside MyClass class, so you should not mock it. RestTemplate is a dependency inside MyClass, so this is exactly what you need to mock. 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. 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;
       }
   }





回答2:


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.
  • 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.



回答3:


It's hard to write a whole test as a lot of information is missing. E.g. what Type is. As you did not posted the name of your class I'm just name it MyClass for now. Also I'm assuming that the RestTemplate is injected via the constructor like

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

Following is a draft for a unit test using JUnit 5 and Mockito. I would suggest mocking the RestTemplate. I have to admit that this way we will not cover to test the usage of MappingJackson2HttpMessageConverter and StringHttpMessageConverter in our test.

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(), "");
    }
}



回答4:


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. 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. As you can see, the url and restTemplate are injected through the constructor. 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 ServiceClasses so that they can be mocked and tested easily.



来源:https://stackoverflow.com/questions/59780621/mockito-unit-testing-resttemplate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!