1

im new to test writing and i am trying to write junit tests for my controller class using mockMvc.

Here are my classes:

public class StudentDTO {

private final String firstName;
private final String lastName;
private final String JMBAG;
private final Integer numberOfECTS;
private final boolean tuitionShouldBePaid;}

Command class

public class StudentCommand { 
@NotBlank (message = "First name must not be empty!")
private String firstName;

@NotBlank (message = "Last name must not be empty!")
private String lastName;


@NotNull(message = "Date of birth must be entered!")
@Past(message = "Date of birth must be in the past!")
private LocalDate dateOfBirth;

@NotBlank(message = "JMBAG must not be empty!")
@Pattern(message = "JMBAG must have 10 digits", regexp = "[\\d]{10}")
private String jmbag;

@NotNull(message = "Number of ECTS points must be entered!")
@PositiveOrZero(message = "Number of ECTS points must be entered as a positive integer!")
private Integer numberOfECTS;}

Controller class:

@Secured({"ROLE_ADMIN"})
@PostMapping
public ResponseEntity<StudentDTO> save(@Valid @RequestBody final StudentCommand command){
    return studentService.save(command)
            .map(
                    studentDTO -> ResponseEntity
                            .status(HttpStatus.CREATED)
                            .body(studentDTO)
            )
            .orElseGet(
                    () -> ResponseEntity
                            .status(HttpStatus.CONFLICT)
                            .build()
            );
}

Test class:

@SpringBootTest
@AutoConfigureMockMvc class StudentControllerTest {
@Autowired
private MockMvc mockMvc;

@MockBean
private StudentService studentServiceMock;

@Autowired
private ObjectMapper objectMapper;

private final String TEST_FIRST_NAME = "Marry";
private final String TEST_LAST_NAME = "Blinks";
private final String TEST_JMBAG = "0025478451";
private final Integer TEST_NUMBER_OF_ECTS = 55;
private final boolean TEST_TUITION_SHOULD_BE_PAID = true;
private final LocalDate TEST_DATE_OF_BIRTH = LocalDate.parse("1999-01-12");

@Test
void testSave() throws Exception {

    StudentCommand studentCommand = new StudentCommand(TEST_FIRST_NAME,TEST_LAST_NAME,TEST_DATE_OF_BIRTH,TEST_JMBAG,TEST_NUMBER_OF_ECTS);

    this.mockMvc.perform(
            post("/student")
                    .with(user("admin")
                            .password("test")
                            .roles("ADMIN")
                    )
                    .with(csrf())
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(studentCommand))
            .accept(MediaType.APPLICATION_JSON)
    )
            .andExpect(status().isCreated())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
            .andExpect(jsonPath("$.jmbag").value(TEST_JMBAG))
            .andExpect(jsonPath("$.firstName").value(TEST_FIRST_NAME))
            .andExpect(jsonPath("$.lastName").value(TEST_LAST_NAME));
}

I always get test failed with this error:


MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /student
       Parameters = {_csrf=[30de7a8f-a3d5-429d-a778-beabd1a533da]}
          Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json", Content-Length:"272"]
             Body = {"firstName":"Marry","lastName":"Blinks","dateOfBirth":{"year":1999,"month":"JANUARY","monthValue":1,"dayOfMonth":12,"chronology":{"id":"ISO","calendarType":"iso8601"},"dayOfWeek":"TUESDAY","leapYear":false,"dayOfYear":12,"era":"CE"},"jmbag":"0025478451","numberOfECTS":55}
    Session Attrs = {}
Handler:
             Type = com.studapp.students.StudentController
           Method = com.studapp.students.StudentController#save(StudentCommand)
MockHttpServletResponse:
           Status = 400
    Error message = null
          Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
java.lang.AssertionError: Status expected:<201> but was:<400>
Expected :201
Actual   :400

I am not sure why it fails. Why is response body blank? I dont want to call my service because im not testing it, but i feel like i should call it somehow(but then again, im not testing the service). Any suggestion would be appreciated.

Sinor Bodl
  • 73
  • 2
  • 11
  • do i need to somehow mock the service so mockMvc can get elements from it ? – Sinor Bodl May 26 '20 at 12:39
  • Could you show the top of the Test class? Which annotations do you use on it and how do you autowire `mockMvc` and `objectMapper`? – Sebastian May 26 '20 at 12:42
  • @Sebastian `@SpringBootTest @AutoConfigureMockMvc class StudentControllerTest { @Autowired private MockMvc mockMvc; private ObjectMapper objectMapper = new ObjectMapper();` So my objectMapper is not annotaited , I dont know it needs something – Sinor Bodl May 26 '20 at 12:53
  • so even when i annotate objecMapper with @Autowire now it shows error Status expected:<201> but was:<409> – Sinor Bodl May 26 '20 at 13:00
  • can you show how are you building each value you are passing to this `new StudentCommand(TEST_FIRST_NAME,TEST_LAST_NAME,TEST_DATE_OF_BIRTH,TEST_JMBAG,TEST_NUMBER_OF_ECTS); ` – Hemant May 26 '20 at 13:14
  • @Hemant i just created variables private final String TEST_FIRST_NAME = "Marry"; private final String TEST_LAST_NAME = "Blinks"; private final String TEST_JMBAG = "0025478451"; private final Integer TEST_NUMBER_OF_ECTS = 55; – Sinor Bodl May 26 '20 at 13:37
  • @Hemant i edited now my test controller class so you can see how it looks – Sinor Bodl May 26 '20 at 13:48
  • 409 seems to be a correct value when looking at your Controller code. It is always better to Autowire the ObjectMapper otherwise it is not configured by Spring in the same way as it would be in the running application – Sebastian May 26 '20 at 15:02

1 Answers1

2

You should use @Autowired on your ObjectMapper to make sure it is configured in the same way by Spring as it would be during the application runtime. This would explain the 400 - Bad request error you are getting.

The fact that it is a 409 - Conflict after autowiring the ObjectMapper suggests that this was indeed the error. Since you do not configure your studentServiceMock in the test, the 409 seems to be the apporiate answer from the controller, because the orElseGet part is being executed.

If I am not mistaken, you could slim down the test class annotations a little and only use @WebMvcTest. This should suffice for this kind of test and it should be a little faster.

Sebastian
  • 858
  • 7
  • 21
  • how would i test it out for when object is saved? – Sinor Bodl May 26 '20 at 15:18
  • Behaviour driven, it would look like something like this: `given(studentServiceMock.save(any(StudentCommand.class).willReturn(studentCommand);` This should be added below this `StudentCommand studentCommand = new StudentCommand......` line in your test. But I did not test this. – Sebastian May 26 '20 at 15:22
  • Oh, it looks like your `save` returns an Optional. In that case it would look like this: `given(studentServiceMock.save(any(StudentCommand.class).willReturn(Optional.of(studentCommand));` – Sebastian May 26 '20 at 15:29
  • i get Ambiguous method call. Both any(Class) in matchers and any (Class) in ArgumentMatchers match. – Sinor Bodl May 26 '20 at 15:32
  • They should be in the `org.mockito.ArgumentMatchers` class. You can simply add a static import: `import static org.mockito.ArgumentMatchers.any;` `given` is from the `org.mockito.BDDMockito` class. – Sebastian May 26 '20 at 15:36
  • i changed it that it returns StudentDTO.I needed to create StudentDto object for that and the test now passes. Hope its an ok test. – Sinor Bodl May 26 '20 at 15:48
  • Ah, sorry, my mistake. I did not check the `save` method return type properly but glad that it works now :) – Sebastian May 26 '20 at 15:57
  • not a problem, just one more thing i wannt to ask you. When creating studentServiceMock, should i initialize it with Mockito.mock(StudentService.class) -all test fail if i do that. Or is it ok with just anotation @MockBean – Sinor Bodl May 26 '20 at 16:02
  • how would i write test for save method for conflict(orElseGet in controler)? tried with `given(studentServiceMock.save(any(StudentCommand.class))).willReturn(Optional.empty());` but it says Status expected:<409> but was:<400> – Sinor Bodl May 26 '20 at 19:32
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/214716/discussion-between-sebastian-and-sinor-bodl). – Sebastian May 27 '20 at 06:52