Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing JSON mapping for a Spring Boot RestTemplate client

I have a REST API outside of my control (supplied by a different, distant team) which I need to consume from a Spring Boot application.

Currently I would like to write a test for that the request (not response) resulting from my RestTemplate invocation corresponds exactly to what is expected at the remote end. I have a sample JSON snippet that I would like to replicate from my code - given the same parameters as in the sample I should get an equivalent JSON snippet in the request body which I would then like to analyze to be certain.

My idea so far is to get RestTemplate to use a server under my control which then captures the JSON request. Apparently MockRestServiceServer is a good choice for this.

Is this the right approach? How do I configure MockRestServiceServer to allow me to do this?

like image 754
Thorbjørn Ravn Andersen Avatar asked Feb 12 '19 13:02

Thorbjørn Ravn Andersen


People also ask

What is difference between getForObject and getForEntity?

For example, the method getForObject() will perform a GET and return an object. getForEntity() : executes a GET request and returns an object of ResponseEntity class that contains both the status code and the resource as an object. getForObject() : similar to getForEntity() , but returns the resource directly.

How do you use postForEntity RestTemplate?

postForEntity. Create a new resource by POSTing the given object to the URL, and returns the response as ResponseEntity . The request parameter can be a HttpEntity in order to add additional HTTP headers to the request. The body of the entity, or request itself, can be a MultiValueMap to create a multipart request.

What is the difference between postForObject and postForEntity?

4.2. Compared to postForObject(), postForEntity() returns the response as a ResponseEntity object. Other than that, both methods do the same job. Let's say that we want to make a POST request to our Person API to create a new Person object and return the response as a ResponseEntity.


2 Answers

If you're only interested in verifying the JSON mapping, you can always use Jackson's ObjectMapper directly and verify if the object structures match by using a library like JSONassert to verify if the serialized string matches your expected result. For example:

@Autowired
private ObjectMapper objectMapper;
private Resource expectedResult = new ClassPathResource("expected.json");

@Test
public void jsonMatches() {
    Foo requestBody = new Foo();
    String json = objectMapper.writeValueAsString(requestBody);
    String expectedJson = Files
        .lines(expectedResult.getFile())
        .collect(Collectors.joining());
    JSONAssert.assertEquals(expectedJson, json, JSONCompareMode.LENIENT);
}

This test purely uses ObjectMapper to verify the JSON mapping and nothing else, so you could even do this without actually having to bootstrap Spring boot within your test (which could be faster). The downside of this is that if you're using a different framework than Jackson, or if RestTemplate changes its implementation, that this test could become obsolete.


Alternatively, if you're interesting in verifying that the complete request matches (both URL, request method, request body and so on), you can use MockRestServiceServer as you mentioned. This can be done by adding the @SpringBootTest annotation to your test, autowiring RestTemplate and the service that invokes RestTemplate for example:

@RunWith(SpringRunner.class)
@SpringBootTest
public class FooServiceTests {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private FooService fooService; // Your service

    private MockRestServiceServer server;

    @Before
    public void setUp() {
        server = MockRestServiceServer.bindTo(restTemplate).build();
    }
}

You can then set up your tests by using:

@Test
public void postUsesRestTemplate() throws IOException, URISyntaxException {
    Path resource = Paths.get(getClass().getClassLoader().getResource("expected-foo.json").toURI());
    String expectedJson = Files.lines(resource).collect(Collectors.joining());
    server.expect(once(), requestTo("http://example.org/api/foo"))
        .andExpect(method(HttpMethod.POST))
        .andExpect(MockRestRequestMatchers.content().json(expectedJson))
        .andRespond(withSuccess());
    // Invoke your service here
    fooService.post();
    server.verify();
}
like image 173
g00glen00b Avatar answered Sep 25 '22 18:09

g00glen00b


As per the documentation, you could match requests using json paths on Mock. For example;

RestTemplate restTemplate = new RestTemplate()
 MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build();

server.expect(ExpectedCount.once(), requestTo(path))
                .andExpect(method(HttpMethod.POST))
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].someField").value("some value"))

Note: I haven't tested this.

But I have achieved what you are looking for using Wire Mock many times. That's again a much better option than MockRestServiceServer. Why do I say so?

  • wide adoption and support
  • more elegant and extensive request & response matching
  • highly configurable
  • record and playback
  • configurable security/auth
  • you could even dockerise this

Have a look at http://wiremock.org/docs/request-matching/

like image 28
Laksitha Ranasingha Avatar answered Sep 22 '22 18:09

Laksitha Ranasingha