Как проверить значения в теле ответа с помощью mockMvc - AssertionError: Ожидаемый статус: ‹201›, но был: ‹400›

Я новичок в написании тестов, и я пытаюсь написать тесты junit для моего класса контроллера, используя mockMvc.

Вот мои классы:

public class StudentDTO {

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

Командный класс

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

Класс контроллера:

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

Тестовый класс:

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

Я всегда получаю тест не пройден с этой ошибкой:


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

Я не уверен, почему это не удается. Почему тело ответа пустое? Я не хочу вызывать свою службу, потому что я ее не тестирую, но я чувствую, что должен как-то ее вызывать (но опять же, я не тестирую службу). Любое предложение будет оценено.


person Sinor Bodl    schedule 26.05.2020    source источник
comment
мне нужно как-то издеваться над сервисом, чтобы mockMvc мог получать от него элементы?   -  person Sinor Bodl    schedule 26.05.2020
comment
Не могли бы вы показать вершину тестового класса? Какие аннотации вы используете для него и как автоматически связываете mockMvc и objectMapper?   -  person Sebastian    schedule 26.05.2020
comment
@Sebastian @SpringBootTest @AutoConfigureMockMvc class StudentControllerTest { @Autowired private MockMvc mockMvc; private ObjectMapper objectMapper = new ObjectMapper(); Итак, мой objectMapper не аннотирован, я не знаю, что ему что-то нужно   -  person Sinor Bodl    schedule 26.05.2020
comment
поэтому, даже когда я аннотирую objecMapper с помощью @Autowire, теперь он показывает ошибку Ожидаемый статус: ‹201›, но был: ‹409›   -  person Sinor Bodl    schedule 26.05.2020
comment
можете ли вы показать, как вы строите каждое значение, которое вы передаете этому new StudentCommand(TEST_FIRST_NAME,TEST_LAST_NAME,TEST_DATE_OF_BIRTH,TEST_JMBAG,TEST_NUMBER_OF_ECTS);   -  person Hemant    schedule 26.05.2020
comment
@Hemant, я только что создал переменные private final String TEST_FIRST_NAME = Marry; закрытая финальная строка TEST_LAST_NAME = мигает; закрытая конечная строка TEST_JMBAG = 0025478451; частное конечное целое число TEST_NUMBER_OF_ECTS = 55;   -  person Sinor Bodl    schedule 26.05.2020
comment
@Hemant Теперь я отредактировал свой класс тестового контроллера, чтобы вы могли увидеть, как он выглядит   -  person Sinor Bodl    schedule 26.05.2020
comment
409 кажется правильным значением, если смотреть на код вашего контроллера. Всегда лучше автоматически связать ObjectMapper, иначе он не будет настроен Spring так же, как в работающем приложении.   -  person Sebastian    schedule 26.05.2020


Ответы (1)


Вы должны использовать @Autowired на своем ObjectMapper, чтобы убедиться, что Spring настроен так же, как и во время выполнения приложения. Это объясняет ошибку 400 — Bad request, которую вы получаете.

Тот факт, что это 409 - Конфликт после автоматического подключения ObjectMapper, предполагает, что это действительно была ошибка. Поскольку вы не настраиваете свой studentServiceMock в тесте, 409 кажется подходящим ответом от контроллера, потому что часть orElseGet выполняется.

Если я не ошибаюсь, вы можете немного уменьшить аннотации тестового класса и использовать только @WebMvcTest. Этого должно быть достаточно для такого рода тестов, и это должно быть немного быстрее.

person Sebastian    schedule 26.05.2020
comment
как бы я проверил это, когда объект сохранен? - person Sinor Bodl; 26.05.2020
comment
В зависимости от поведения это будет выглядеть примерно так: given(studentServiceMock.save(any(StudentCommand.class).willReturn(studentCommand); Это должно быть добавлено ниже этой StudentCommand studentCommand = new StudentCommand...... строки в вашем тесте. Но я не проверял это. - person Sebastian; 26.05.2020
comment
О, похоже, ваш save возвращает необязательное значение. В этом случае это будет выглядеть так: given(studentServiceMock.save(any(StudentCommand.class).willReturn(Optional.of(studentCommand)); - person Sebastian; 26.05.2020
comment
я получаю неоднозначный вызов метода. И any(Class‹StudentCommand›) в сопоставителях, и any (Class‹StudentCommand›) в ArgumentMatchers совпадают. - person Sinor Bodl; 26.05.2020
comment
Они должны быть в классе org.mockito.ArgumentMatchers. Вы можете просто добавить статический импорт: import static org.mockito.ArgumentMatchers.any; given из класса org.mockito.BDDMockito. - person Sebastian; 26.05.2020
comment
я изменил его, чтобы он возвращал StudentDTO. Мне нужно было создать для этого объект StudentDto, и теперь тест проходит. Надеюсь, это нормальный тест. - person Sinor Bodl; 26.05.2020
comment
Ах, простите, моя ошибка. Я не проверял должным образом тип возвращаемого значения метода save, но рад, что теперь он работает :) - person Sebastian; 26.05.2020
comment
не проблема, просто еще одна вещь, которую я хочу спросить у вас. При создании studentServiceMock, должен ли я инициализировать его с помощью Mockito.mock(StudentService.class) - все тесты не пройдут, если я это сделаю. Или все в порядке с аннотацией @MockBean? - person Sinor Bodl; 26.05.2020
comment
как мне написать тест для метода сохранения для конфликта (или ElseGet в контроллере)? пытался с given(studentServiceMock.save(any(StudentCommand.class))).willReturn(Optional.empty());, но он говорит Ожидаемый статус: ‹409›, но был: ‹400› - person Sinor Bodl; 26.05.2020
comment
Давайте продолжим обсуждение в чате. - person Sebastian; 27.05.2020