TestRestTemplate позволяет писать тесты для REST API. Здесь мы запускаем веб-сервер, делаем запрос — все по-настоящему.
Ниже мы рассмотрим примеры использования TestRestTemplate.
Что тестируем
Тестировать будем простой контроллер, описанный в статье Spring Boot REST API. Он предоставляет REST-сервис для операций получения, создания, редактирования и удаления сущности Person.
Тестовый класс и его аннотации
@after
Предполагается, что база данных для тестов — отдельная, поэтому после каждого теста мы смело очищаем ее в методе resetDb():
@After public void resetDb() { repository.deleteAll(); }
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@SpringBootTest поднимает весь контекст. А значение webEnvironment = WebEnvironment.RANDOM_PORT) запускает сервер Tomcat на случайном порту, а также создает бин TestRestTemplate, который мы можем просто внедрить в тестовый класс:
@Autowired private TestRestTemplate restTemplate;
createTestPerson()
Этот метод вынесен отдельно, так как в начале тестов мы часто создаем в базе Person.
Вот так сокращенно выглядит тестовый класс (некоторые тесты перенесены ниже отдельно, весь код на GitHub):
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class PersonControllerIntegrationTest { @Autowired private TestRestTemplate restTemplate; @Autowired private PersonRepository repository; @After public void resetDb() { repository.deleteAll(); } @Test public void whenCreatePerson_thenStatus201() { Person person = new Person("Michail"); ResponseEntity<Person> response = restTemplate.postForEntity("/persons", person, Person.class); assertThat(response.getStatusCode(), is(HttpStatus.CREATED)); assertThat(response.getBody().getId(), notNullValue()); assertThat(response.getBody().getName(), is("Michail")); } //..другие тесты private Person createTestPerson(String name) { Person emp = new Person(name); return repository.saveAndFlush(emp); } }
postForEntity() и getForEntity() — Получение JSON
Методы postForEntity(), getForEntity() позволяют вернуть ResponseEntity. Имея этот класс, можно проверить и код ответа, и тело ответа и все прочее:
@Test public void whenCreatePerson_thenStatus201() { Person person = new Person("Michail"); ResponseEntity<Person> response = restTemplate.postForEntity("/persons", person, Person.class); assertThat(response.getStatusCode(), is(HttpStatus.CREATED)); assertThat(response.getBody().getId(), notNullValue()); assertThat(response.getBody().getName(), is("Michail")); }
Тут мы проверяем, что Person создался, вновь созданный объект вернулся в JSON и код ответа 201(created).
getForObject(), postForObject() — Получение Бина
Если нужно только тело ответа, то можно сделать еще проще. Метод getForObject() сразу преобразует JSON в объект:
@Test public void givenPerson_whenGetPerson_thenStatus200() { long id = createTestPerson("Joe").getId(); Person person = restTemplate.getForObject("/persons/{id}", Person.class, id); assertThat(person.getName(), is("Joe")); }
Здесь мы получаем бин по идентификатору и проверяем, что поле name соответствует.
Обратите внимание, что параметры запроса прописываются в фигурных скобках и затем передаются как аргументы в самом конце в сигнатуре метода (столько штук, сколько надо).
exchange()
Редактирование Person будем тестировать с помощью метода exchange(). Это самый гибкий метод, в котором можно передать и тип запроса (GET, POST, PUT..), и HttpEntity, и параметры. Возвращает он ResponseEntity:
@Test public void whenUpdatePerson_thenStatus200() { long id = createTestPerson("Nick").getId(); Person person = new Person("Michail"); HttpEntity<Person> entity = new HttpEntity<Person>(person); ResponseEntity<Person> response = restTemplate.exchange("/persons/{id}", HttpMethod.PUT, entity, Person.class, id); assertThat(response.getStatusCode(), is(HttpStatus.OK)); assertThat(response.getBody().getId(), notNullValue()); assertThat(response.getBody().getName(), is("Michail")); }
Получение списка
Со списком не все так просто. Для того, чтобы не потерять тип элементов возвращаемого списка, мы должны создать и передать экземпляр анонимного класса:
new ParameterizedTypeReference<List<Person>>() {}
Весь код такой:
@Test public void givenPersons_whenGetPersons_thenStatus200() { createTestPerson("Joe"); createTestPerson("Jane"); ResponseEntity<List<Person>> response = restTemplate.exchange("/persons", HttpMethod.GET, null, new ParameterizedTypeReference<List<Person>>() { }); List<Person> persons = response.getBody(); assertThat(persons, hasSize(2)); assertThat(persons.get(1).getName(), is("Jane")); }
Здесь мы создаем два элемента Person в базе, получаем список и проверяем, что он состоит из двух элементов. Также тестируем поле name второго элемента.
TestRestTemplate vs. MockMvc
С TestRestTemplate мы по-настоящему запускаем сервер, тогда как с MockMvc — нет.
Кроме того, представьте, что наше приложение не предоставляет REST API, а использует его: то есть делает запросы с помощью RestTemplate. В смысле является клиентом. Тогда мы наверняка хотим проверить, что возвращает сервер — с помощью TestRestTemplate.
Итог
Полный исходный код примеров можно скачать на GitHub.
Более сложный случай тестирования защищенных url тут.
Тестирование этого же приложения с помощью REST-assured описано здесь.
Тестирование с помощью MockMvc тут.
Как лайк поставить?
Здравствуйте! Подскажите, пожалуйста, как разобраться с проблемой: застреваю на первом же примере, на тесте create. У меня чуть сложнее, при создании объекта пользователь должен быть авторизован. Если я использую testRestTemplate.withBasicAuth — тест падает на статусе, возвращается 302 вместо 201. Основной момент — в объект при создании должен положиться юзер, в контроллере он берется из принципала, который, видимо, при withBasicAuth не создается. Предполагаю, что проблема именно в этом, но как это обойти не представляю.
@Test
public void create_testStatus201(){
Blog blog = new Blog();
blog.setTitle(«Test title»);
blog.setContent(«Test Content»);
ResponseEntity response = testRestTemplate.withBasicAuth(«admin»,»admin»)
.postForEntity(«/blog/create»,blog, BlogDto.class);
assertThat(response.getStatusCode(), is(HttpStatus.CREATED)); // падает тут
assertThat(response.getBody().getId(),notNullValue());
assertThat(response.getBody().getTitle(), is(«Test title»));
}
@PostMapping(«/blog/create»)
public ResponseEntity create(@Valid @RequestBody Blog blog, Principal principal) {
ModelMapper mapper = new ModelMapper();
blog.setUser(userRepository.findByUsername(principal.getName()));
return ResponseEntity.status(201).body(mapper.map(blogRepository.save(blog), BlogDto.class));
}
Да, если авторизация не .httpBasic(), то .withBasicAuth() не сработает.
Можно добавлять вручную в запрос хидер авторизации. В тесте делать дополнительный запрос ради получения этого хидера (пример теста /user для приложения с jwt):
@Test
public void whenGetUser_thenCorrect() {
AuthResponse authResponse = getAuthHeaderForUser("user", "password"); //здесь дополнительный запрос на открытый адрес /authenticate
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer "+authResponse.getJwtToken() );
ResponseEntity response = restTemplate.exchange("/user", HttpMethod.GET, new HttpEntity<>(headers), String.class); //теперь тут + headers
assertTrue(response.getBody().equals("User"));
}
Если у вас сессии, то аналогично хидер Cookie извлечь и добавить.
@WithMockUser насколько знаю с MockMvc используется. Не с TestRestTemplate.
Добавлен тест для примера с пользовательской аутентификацией https://github.com/myluckagain/sysout/tree/master/testresttemplate-with-auth
Объяснение https://sysout.ru/testresttemplate-s-avtorizatsiej/
Спасибо огромное! Сейчас снова пришлось вернуться к проблеме, оказалось, ошибка была в другом. Там, где была авторизация, возвращался 302 код респонза, простите меня, пожалуйста)))