Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Тесты: @QuarkusTest и RestAssured

Проверять API вручную через curl после каждого изменения утомительно. Тест делает то же самое автоматически: отправляет запросы и сравнивает ответы с ожидаемыми. В Quarkus тест с аннотацией @QuarkusTest запускает настоящее приложение — с базой, бинами и настройками профиля test, — а запросы к нему удобно писать библиотекой RestAssured. Обе зависимости генератор уже добавил в pom.xml: quarkus-junit и rest-assured.

Первый тест

Тесты лежат в src/test/java в том же пакете, что и код:

// src/test/java/com/example/blog/PostResourceTest.java
package com.example.blog;
 
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
 
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
 
@QuarkusTest
class PostResourceTest {
 
    @Test
    void listsPosts() {
        given()
                .when().get("/posts")
                .then()
                .statusCode(200)
                .body("size()", greaterThanOrEqualTo(2));
    }
 
    @Test
    void createsPost() {
        given()
                .contentType(ContentType.JSON)
                .body(new NewPost("Тестовый пост", "Написан тестом"))
                .when().post("/posts")
                .then()
                .statusCode(201)
                .body("id", notNullValue())
                .body("title", is("Тестовый пост"));
    }
 
    @Test
    void returns404ForUnknownPost() {
        given()
                .when().get("/posts/999")
                .then()
                .statusCode(404)
                .body("error", is("Пост 999 не найден"));
    }
}

Цепочка читается как фраза: «дано — когда GET /posts — тогда статус 200 и в теле не меньше двух элементов». body("title", is(...)) проверяет поле JSON-ответа, size() — длину массива. Объект NewPost RestAssured сам превращает в JSON.

Запуск

./mvnw test

Вывод сокращён:

INFO  [io.quarkus.datasource.deployment.devservices.DevServicesDatasourceProcessor] Dev Services for default datasource (h2) started
INFO  [io.quarkus] blog 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.40.1) started in 12.001s. Listening on: http://localhost:8081
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 -- in com.example.blog.PostResourceTest
[INFO] BUILD SUCCESS

Приложение стартует один раз на все тесты, на порту 8081, чтобы не мешать dev mode на 8080. Базу для тестов снова поднимает Dev Services, а Startup заполняет её двумя постами — поэтому проверка greaterThanOrEqualTo(2) проходит.

Непрерывное тестирование

В запущенном quarkus:dev нажмите r. Quarkus прогонит тесты и дальше будет перезапускать их после каждого сохранения файла — только те, которые затрагивает изменение. Результат виден внизу терминала и в разделе Continuous Testing в Dev UI.

Подмена REST-клиента

Тест не должен зависеть от чужого сервиса в интернете. Бин можно подменить моком — объектом-заглушкой. Добавьте зависимость:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit-mockito</artifactId>
    <scope>test</scope>
</dependency>
// src/test/java/com/example/blog/AuthorResourceTest.java
package com.example.blog;
 
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
 
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
 
@QuarkusTest
class AuthorResourceTest {
 
    @InjectMock
    @RestClient
    UserClient userClient;
 
    @Test
    void returnsAuthorFromUsersService() {
        Mockito.when(userClient.getById(1))
                .thenReturn(new User(1, "Анна", "anna@example.com"));
 
        given()
                .when().get("/authors/1")
                .then()
                .statusCode(200)
                .body("name", is("Анна"));
    }
}

@InjectMock заменяет настоящий клиент на мок только в этом тесте, а Mockito.when(...).thenReturn(...) задаёт, что он вернёт. Запрос в интернет не уходит.

Частая ошибка

Если забыть .contentType(ContentType.JSON) в тесте createsPost, он упадёт:

[ERROR] PostResourceTest.createsPost:31 1 expectation failed.
Expected status code <201> but was <415>.

Это та же ошибка 415 Unsupported Media Type, что и с curl без заголовка: сервер не знает, что тело — JSON. Номер строки :31 указывает на проверку, которая не прошла.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.