The best way to verify the correct JSON
response depends on what your response looks like.
For example, let's say that you have a @Controller
(or @RestController
) that accesses a @Repository
(either directly or indirectly via a @Service
) and maps the resulting item to JSON
. For example:
public class MyPojo {
private int id;
private String name;
private int age;
// no-args for deserialization, required by some Frameworks with default settings
public MyPojo() {}
// constructor
public MyPojo(final int id, final String name, final int age) { ... }
// Getters
public int getid() { ... }
public String getName() { ... }
public int getName() { ... }
}
@Repository
public class MyRepository {
public MyPojo findMyPojoById(final int id) { return db.findById(id); }
}
@RestController
@RequestMapping("/")
public class MyController {
@Autowired
private MyRepository repository;
@GetMapping
public MyPojo findMyPojoById(@RequestParam final int id) {
return repository.findMyPojoByid(id);
}
}
Then a GET
request with ?id=1
would perhaps return something like the following:
{
"id": 1,
"name": "John",
"age": 12
}
In this case, you have multiple choices. In a mockMvc test, you are usually not interested in testing the integration of the components and only wish to test that your controller is working as expected. In this case, you would probably want a unit-test, and mock the repository. You have some options:
1. Via a mocked expected response
In this case you can do something like:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcExampleTests {
@Autowired
private MockMvc mvc;
@MockBean
private MyRepository repository; // mock the repository
@Autowired
private ObjectMapper objectMapper;
@Test
public void exampleTest() throws Exception {
final int userId = 1;
MyPojo mockedUser = new MyPojo(userId, "John", 12);
Mockito.doReturn(mockedUser).when(repository).findMyPojoById(userId);
final String expectedResponseContent = objectMapper.writeValueAsString(mockedUser);
this.mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(content().json(expectedResponseContent ));
verify(repository).findMyPojoById(userId); // verify that the repository was called correctly
}
}
The upside of this is that the tests are robuster. If your MyPojo
object changes, your tests would fail as long as you do not update all constructor usages of your mocked objects in test - a moot point, as the code would not compile until you do.
2. Via jsonPath()
In this case, you can use jsonPath
(a way to evaluate expressions on JSON
structures, similar to XPath
for XML
) to individually evaluate some or all parts of a JSON
structure.
For example (still mocking the response):
@Test
public void exampleTest() throws Exception {
final int userId = 1;
MyPojo mockedUser = new MyPojo(userId, "John", 12);
Mockito.doReturn(mockedUser).when(repository).findMyPojoById(userId); // note that this mock is not necessary, but it does make the test a unit test
this.mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is(userId))) // is(<Object>) is e.g. a Hamcrest Matcher
.andExpect(jsonPath("$.name", is("John"))
.andExpect(jsonPath("$.age", is(12)))
verify(repository).findMyPojoById(userId);
}
Note that if you had e.g. an array of response objects, you could parse them with the proper array notation, e.g.:
.andExpect(jsonPath("$[0].name", is("Mary")))
The upside of this is that you do not have to evaluate the entire response. This may be useful if, for example, the server response includes server-generated data that is difficult to replicate or mock without doing away with too much of the actual code logic (via e.g. mocking). In this case, you could check for the presence of the field, without regard for its actual value, e.g.:
.andExpect(jsonPath("$.serverGeneratedSecretKey", notNullValue()))
3. Via string matching
Lastly, you might compare the actual response:
String expectedResponse = "{"id": 1, "name":"John", "age": 12}";
String responseString = mockMvc.perform(...)
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
assertEquals("Response does not match", expectedResponse, responseString);
However, this way is very fickle:
- You need to take match the
JSON
exactly, which includes escaping the "
's and proper whitespacing.
- While e.g.
ObjectMapper
tries to honor the ordering of the JSON fields as they appear, you should never base your code working based on the ordering of a JSON object (aside from arrays, ordering in JSON
is not defined)
- This approach also makes refactoring a nightmare. Adding a field breaks your test, making you have to change the expected string - error-prone due to the above reasons.
I can't think of a very good use case for this approach, but, well - it's there if you want it.
It might be interesting to note that Java 13
finally supports text blocks. These blocks, while they make it easier to write the expected response string, does not help with the other points.