REST Assured: полное руководство по тестированию API на Java
Изучаем REST Assured для автоматизации API тестирования: установка, базовые запросы, валидация JSON, аутентификация, десериализация в классы, спецификации и практические паттерны.
Введение в REST Assured
REST Assured — самая популярная библиотека для тестирования REST API на Java. Предоставляет fluent API для отправки HTTP-запросов и валидации ответов. В этом руководстве разберём не только базовые возможности, но и продвинутые техники, которые используются в реальных проектах.
Установка и настройка
Добавьте зависимости в pom.xml:
1<dependencies>
2 <dependency>
3 <groupId>io.rest-assured</groupId>
4 <artifactId>rest-assured</artifactId>
5 <version>5.4.0</version>
6 <scope>test</scope>
7 </dependency>
8 <dependency>
9 <groupId>com.fasterxml.jackson.core</groupId>
10 <artifactId>jackson-databind</artifactId>
11 <version>2.16.1</version>
12 <scope>test</scope>
13 </dependency>
14 <dependency>
15 <groupId>org.junit.jupiter</groupId>
16 <artifactId>junit-jupiter</artifactId>
17 <version>5.10.1</version>
18 <scope>test</scope>
19 </dependency>
20</dependencies>Базовые HTTP-запросы
1import static io.restassured.RestAssured.*;
2import static org.hamcrest.Matchers.*;
3
4@Test
5public void getUserTest() {
6 given()
7 .when()
8 .get("https://jsonplaceholder.typicode.com/users/1")
9 .then()
10 .statusCode(200)
11 .body("name", equalTo("Leanne Graham"))
12 .body("email", containsString("@"));
13}Десериализация ответа в Java-классы
Вместо работы с JSON как со строкой, лучше десериализовать ответ в типизированные классы. Это даёт автодополнение IDE и защиту от ошибок на этапе компиляции.
Создание POJO-классов
1// User.java
2public class User {
3 private Long id;
4 private String name;
5 private String username;
6 private String email;
7 private Address address;
8 private String phone;
9 private String website;
10 private Company company;
11
12 // Конструкторы, геттеры и сеттеры
13 public User() {}
14
15 public Long getId() { return id; }
16 public void setId(Long id) { this.id = id; }
17
18 public String getName() { return name; }
19 public void setName(String name) { this.name = name; }
20
21 // ... остальные геттеры и сеттеры
22}
23
24// Address.java
25public class Address {
26 private String street;
27 private String suite;
28 private String city;
29 private String zipcode;
30 private Geo geo;
31
32 // Конструкторы, геттеры и сеттеры
33}
34
35// Company.java
36public class Company {
37 private String name;
38 private String catchPhrase;
39 private String bs;
40
41 // Конструкторы, геттеры и сеттеры
42}Десериализация в тестах
1@Test
2public void getUserWithDeserialization() {
3 User user = given()
4 .when()
5 .get("https://jsonplaceholder.typicode.com/users/1")
6 .then()
7 .statusCode(200)
8 .extract()
9 .as(User.class);
10
11 assertThat(user.getId()).isEqualTo(1L);
12 assertThat(user.getName()).isEqualTo("Leanne Graham");
13 assertThat(user.getEmail()).contains("@");
14}
15
16@Test
17public void getUsersList() {
18 List<User> users = given()
19 .when()
20 .get("https://jsonplaceholder.typicode.com/users")
21 .then()
22 .statusCode(200)
23 .extract()
24 .jsonPath()
25 .getList(".", User.class);
26
27 assertThat(users).hasSize(10);
28 assertThat(users.get(0).getName()).isEqualTo("Leanne Graham");
29}Использование спецификаций (RequestSpecification и ResponseSpecification)
Спецификации позволяют переиспользовать общие настройки запросов и проверок ответов. Это делает тесты более читаемыми и поддерживаемыми.
1public class ApiSpecifications {
2
3 public static RequestSpecification requestSpec() {
4 return new RequestSpecBuilder()
5 .setBaseUri("https://jsonplaceholder.typicode.com")
6 .setContentType(ContentType.JSON)
7 .addHeader("User-Agent", "REST-Assured-Tests")
8 .setRelaxedHTTPSValidation()
9 .build();
10 }
11
12 public static ResponseSpecification responseSpec200() {
13 return new ResponseSpecBuilder()
14 .expectStatusCode(200)
15 .expectContentType(ContentType.JSON)
16 .expectResponseTime(lessThan(3000L))
17 .build();
18 }
19
20 public static ResponseSpecification responseSpec201() {
21 return new ResponseSpecBuilder()
22 .expectStatusCode(201)
23 .expectContentType(ContentType.JSON)
24 .build();
25 }
26}Использование спецификаций в тестах:
1@Test
2public void getUserWithSpec() {
3 User user = given()
4 .spec(ApiSpecifications.requestSpec())
5 .when()
6 .get("/users/1")
7 .then()
8 .spec(ApiSpecifications.responseSpec200())
9 .extract()
10 .as(User.class);
11
12 assertThat(user.getName()).isEqualTo("Leanne Graham");
13}
14
15@Test
16public void createUser() {
17 User newUser = new User();
18 newUser.setName("John Doe");
19 newUser.setEmail("john@example.com");
20
21 User createdUser = given()
22 .spec(ApiSpecifications.requestSpec())
23 .body(newUser)
24 .when()
25 .post("/users")
26 .then()
27 .spec(ApiSpecifications.responseSpec201())
28 .extract()
29 .as(User.class);
30
31 assertThat(createdUser.getName()).isEqualTo("John Doe");
32}Фильтры для логирования и обработки запросов
Фильтры позволяют добавить сквозную функциональность: логирование, аутентификацию, модификацию запросов.
1// Фильтр для логирования
2public class LoggingFilter implements Filter {
3 @Override
4 public Response filter(FilterableRequestSpecification requestSpec,
5 FilterableResponseSpecification responseSpec,
6 FilterContext ctx) {
7
8 System.out.println("=== REQUEST ===");
9 System.out.println(requestSpec.getMethod() + " " + requestSpec.getURI());
10 System.out.println("Headers: " + requestSpec.getHeaders());
11
12 Response response = ctx.next(requestSpec, responseSpec);
13
14 System.out.println("=== RESPONSE ===");
15 System.out.println("Status: " + response.getStatusCode());
16 System.out.println("Body: " + response.getBody().asString());
17
18 return response;
19 }
20}
21
22// Фильтр для добавления токена аутентификации
23public class AuthFilter implements Filter {
24 private final String token;
25
26 public AuthFilter(String token) {
27 this.token = token;
28 }
29
30 @Override
31 public Response filter(FilterableRequestSpecification requestSpec,
32 FilterableResponseSpecification responseSpec,
33 FilterContext ctx) {
34 requestSpec.header("Authorization", "Bearer " + token);
35 return ctx.next(requestSpec, responseSpec);
36 }
37}Использование фильтров:
1@Test
2public void testWithFilters() {
3 given()
4 .filter(new LoggingFilter())
5 .filter(new AuthFilter("your-jwt-token"))
6 .when()
7 .get("https://api.example.com/protected-endpoint")
8 .then()
9 .statusCode(200);
10}Группировка API-методов в сервисные классы
В реальных проектах API-вызовы группируют по доменам в отдельные сервисные классы. Это улучшает переиспользование кода и поддержку.
1// UserApiService.java
2public class UserApiService {
3 private static final String BASE_PATH = "/users";
4
5 public User getUserById(Long id) {
6 return given()
7 .spec(ApiSpecifications.requestSpec())
8 .pathParam("id", id)
9 .when()
10 .get(BASE_PATH + "/{id}")
11 .then()
12 .spec(ApiSpecifications.responseSpec200())
13 .extract()
14 .as(User.class);
15 }
16
17 public List<User> getAllUsers() {
18 return given()
19 .spec(ApiSpecifications.requestSpec())
20 .when()
21 .get(BASE_PATH)
22 .then()
23 .spec(ApiSpecifications.responseSpec200())
24 .extract()
25 .jsonPath()
26 .getList(".", User.class);
27 }
28
29 public User createUser(User user) {
30 return given()
31 .spec(ApiSpecifications.requestSpec())
32 .body(user)
33 .when()
34 .post(BASE_PATH)
35 .then()
36 .spec(ApiSpecifications.responseSpec201())
37 .extract()
38 .as(User.class);
39 }
40
41 public User updateUser(Long id, User user) {
42 return given()
43 .spec(ApiSpecifications.requestSpec())
44 .pathParam("id", id)
45 .body(user)
46 .when()
47 .put(BASE_PATH + "/{id}")
48 .then()
49 .statusCode(200)
50 .extract()
51 .as(User.class);
52 }
53
54 public void deleteUser(Long id) {
55 given()
56 .spec(ApiSpecifications.requestSpec())
57 .pathParam("id", id)
58 .when()
59 .delete(BASE_PATH + "/{id}")
60 .then()
61 .statusCode(200);
62 }
63}Использование сервисных классов в тестах
1public class UserApiTest {
2 private UserApiService userService;
3
4 @BeforeEach
5 void setUp() {
6 userService = new UserApiService();
7 }
8
9 @Test
10 void shouldGetUserById() {
11 User user = userService.getUserById(1L);
12
13 assertThat(user.getId()).isEqualTo(1L);
14 assertThat(user.getName()).isEqualTo("Leanne Graham");
15 }
16
17 @Test
18 void shouldCreateAndDeleteUser() {
19 // Создаём пользователя
20 User newUser = new User();
21 newUser.setName("Test User");
22 newUser.setEmail("test@example.com");
23
24 User createdUser = userService.createUser(newUser);
25 assertThat(createdUser.getName()).isEqualTo("Test User");
26
27 // Удаляем пользователя
28 userService.deleteUser(createdUser.getId());
29 }
30
31 @Test
32 void shouldGetAllUsers() {
33 List<User> users = userService.getAllUsers();
34
35 assertThat(users).isNotEmpty();
36 assertThat(users).hasSize(10);
37 }
38}Работа с аутентификацией
1// Basic Auth
2@Test
3public void testBasicAuth() {
4 given()
5 .auth().basic("username", "password")
6 .when()
7 .get("/protected")
8 .then()
9 .statusCode(200);
10}
11
12// Bearer Token
13@Test
14public void testBearerToken() {
15 given()
16 .auth().oauth2("your-access-token")
17 .when()
18 .get("/protected")
19 .then()
20 .statusCode(200);
21}
22
23// Custom Header
24@Test
25public void testCustomAuth() {
26 given()
27 .header("X-API-Key", "your-api-key")
28 .when()
29 .get("/protected")
30 .then()
31 .statusCode(200);
32}Валидация сложных JSON-структур
1@Test
2public void validateComplexJson() {
3 given()
4 .when()
5 .get("/users")
6 .then()
7 .statusCode(200)
8 .body("size()", greaterThan(0))
9 .body("[0].id", notNullValue())
10 .body("[0].name", not(emptyString()))
11 .body("[0].address.city", notNullValue())
12 .body("findAll { it.email.contains('@') }.size()", equalTo(10));
13}
14
15@Test
16public void validateWithJsonPath() {
17 Response response = given()
18 .when()
19 .get("/users")
20 .then()
21 .statusCode(200)
22 .extract()
23 .response();
24
25 JsonPath jsonPath = response.jsonPath();
26 List<String> names = jsonPath.getList("name");
27 List<String> emails = jsonPath.getList("email");
28
29 assertThat(names).hasSize(10);
30 assertThat(emails).allMatch(email -> email.contains("@"));
31}Конфигурация и лучшие практики
Настройка глобальных параметров REST Assured:
1@BeforeAll
2static void globalSetup() {
3 RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
4 RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
5 RestAssured.config = RestAssuredConfig.config()
6 .objectMapperConfig(
7 ObjectMapperConfig.objectMapperConfig()
8 .jackson2ObjectMapperFactory((cls, charset) -> {
9 ObjectMapper mapper = new ObjectMapper();
10 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
11 return mapper;
12 })
13 );
14}Заключение
REST Assured — мощный инструмент для API-тестирования на Java. Основные принципы для продуктивной работы:
- ▸Используйте десериализацию в POJO вместо работы со строками JSON
- ▸Выносите общие настройки в спецификации
- ▸Группируйте API-методы в сервисные классы по доменам
- ▸Применяйте фильтры для сквозной функциональности
- ▸Настраивайте глобальную конфигурацию один раз
- ▸Используйте JsonPath для сложной валидации JSON
Эти паттерны помогут создать поддерживаемые и масштабируемые API-тесты. На курсах ThreadQA по Java QA Automation REST Assured изучается как основной инструмент для тестирования API.