I have the following method in a controller class:
@PostMapping("employees")
@ResponseStatus(HttpStatus.CREATED)
public Employee addEmployee(@Valid @RequestBody Employee employee) {
try {
return employeeRepository.save(employee);
} catch (DataIntegrityViolationException e) {
e.printStackTrace();
Optional<Employee> existingEmployee = employeeRepository.findByTagId(employee.getTagId());
if (!existingEmployee.isPresent()) {
//The exception root cause was not due to a unique ID violation then
throw e;
}
throw new DuplicateEntryException(
"An employee named " + existingEmployee.get().getName() + " already uses RFID tagID " + existingEmployee.get().getTagId());
}
}
Where the Employee
class has a string field called tagId
which has a @NaturalId
annotation on it. (Please ignore that there is no dedicated service layer, this is a small and simple app).
Here is my custom DuplicateEntryException
:
@ResponseStatus(HttpStatus.CONFLICT)
public class DuplicateEntryException extends RuntimeException {
public DuplicateEntryException() {
super();
}
public DuplicateEntryException(String message) {
super(message);
}
public DuplicateEntryException(String message, Throwable cause) {
super(message, cause);
}
}
Thanks to the @ResponseStatus(HttpStatus.CONFLICT)
line, when I manually test the method, I get the default spring boot REST message with the timestamp, status, error, message and path fields.
I'm still getting familiar with testing in Spring and I have this test:
@Test
public void _02addEmployee_whenDuplicateTagId_thenExceptionIsReturned() throws Exception {
Employee sampleEmployee = new Employee("tagId01", "John Doe");
System.out.println("Employees in the database: " + repository.findAll().size()); //prints 1
// @formatter:off
mvc.perform(post("/employees").contentType(MediaType.APPLICATION_JSON).content(JsonUtil.toJson(sampleEmployee)))
.andExpect(status().isConflict())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.message").value("An employee named John Doe already uses RFID tagID tagId01"));
// @formatter:on
int employeeCount = repository.findAll().size();
Assert.assertEquals(1, employeeCount);
}
As you can guess, there is another test that runs first, called _01addEmployee_whenValidInput_thenCreateEmployee()
, which inserts an employee with the same tagID, which is used in test #2. Test #1 passes, but test #2 does not, because the HTTP response looks like this:
MockHttpServletResponse:
Status = 409
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
And in the console before the above response, I see this:
Resolved Exception:
Type = ai.aitia.rfid_employee.exception.DuplicateEntryException
So my 2nd test fails because java.lang.AssertionError: Content type not set
.
What causes the different behaviour compared to the manual testing? Why isn't this returned?
{
"timestamp": "2019-01-03T09:47:33.371+0000",
"status": 409,
"error": "Conflict",
"message": "An employee named John Doe already uses RFID tagID tagId01",
"path": "/employees"
}
Update: I experienced the same thing with a different REST endpoint as well, where the test case resulted in my own ResourceNotFoundException
, but the actual JSON error object was not received by the MockMvc object.
Update2: Here are my class level annotations for the test class:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = RfidEmployeeApplication.class)
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@TestPropertySource(locations = "classpath:application-test.properties")