Тестирование REST API с помощью TestRestTemplate

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 тут.

Тестирование REST API с помощью TestRestTemplate: 7 комментариев

  1. Здравствуйте! Подскажите, пожалуйста, как разобраться с проблемой: застреваю на первом же примере, на тесте 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));
    }

    1. Да, если авторизация не .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"));

      }

      1. Спасибо огромное! Сейчас снова пришлось вернуться к проблеме, оказалось, ошибка была в другом. Там, где была авторизация, возвращался 302 код респонза, простите меня, пожалуйста)))

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *