Encodera

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

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

Оглавление

Тесты с javalin-testtools

До сих пор мы проверяли API руками через curl. Автотест делает то же самое программой: запускает сервер, отправляет запросы и сравнивает ответы с ожидаемыми. Тесты запускаются одной командой и сразу показывают, что сломалось после изменений. Для Javalin есть модуль javalin-testtools: он запускает приложение на свободном порту, даёт готовый HTTP-клиент и останавливает сервер после теста.

Зависимости

Добавьте в pom.xml модуль и JUnit с областью test — они нужны только тестам:

<!-- pom.xml -->
<dependency>
    <groupId>io.javalin</groupId>
    <artifactId>javalin-testtools</artifactId>
    <version>${javalin.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>6.1.3</version>
    <scope>test</scope>
</dependency>

И плагин, который запускает тесты, в раздел plugins:

<!-- pom.xml -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.5.6</version>
</plugin>

Приложение без запуска

Тесту нужен объект Javalin, ещё не запущенный и со своей базой. Вынесите создание приложения из main в метод createApp:

// src/main/java/com/example/blog/Main.java
public static void main(String[] args) {
    var repo = new PostRepository(Database.create("jdbc:h2:./blog"));
    createApp(repo).start(8080);
}
 
public static Javalin createApp(PostRepository repo) {
    var events = new PostEvents();
    var posts = new PostController(repo, events);
    var chat = new Chat();
 
    return Javalin.create(config -> {
        // все настройки и маршруты из прошлых уроков без изменений
    });
}

main по-прежнему запускает сервер на порту 8080 с файловой базой, а тест передаст в createApp базу в памяти.

Тест

Создайте класс в папке src/test/java:

// src/test/java/com/example/blog/PostApiTest.java
package com.example.blog;
 
import io.javalin.Javalin;
import io.javalin.testtools.JavalinTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
 
import java.util.Base64;
import java.util.UUID;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
 
class PostApiTest {
    private Javalin app;
 
    @BeforeEach
    void setUp() {
        String url = "jdbc:h2:mem:" + UUID.randomUUID() + ";DB_CLOSE_DELAY=-1";
        app = Main.createApp(new PostRepository(Database.create(url)));
    }
 
    @Test
    void listIsEmptyAtStart() {
        JavalinTest.test(app, (server, client) -> {
            var response = client.get("/posts");
            assertEquals(200, response.code());
            assertEquals("[]", response.body().string());
        });
    }
 
    @Test
    void createNeedsLogin() {
        JavalinTest.test(app, (server, client) -> {
            var post = new NewPost("Тест", "Немного текста", "Анна");
            assertEquals(401, client.post("/posts", post).code());
        });
    }
 
    @Test
    void annaCreatesPost() {
        JavalinTest.test(app, (server, client) -> {
            var post = new NewPost("Тест", "Немного текста", "Анна");
            String auth = "Basic " + Base64.getEncoder().encodeToString("anna:changeme".getBytes());
            var response = client.post("/posts", post, req -> req.header("Authorization", auth));
            assertEquals(201, response.code());
            assertTrue(response.body().string().contains("\"title\":\"Тест\""));
        });
    }
}

Перед каждым тестом @BeforeEach создаёт приложение с новой пустой базой — тесты не мешают друг другу. JavalinTest.test запускает сервер, передаёт в лямбду клиент и останавливает сервер в конце. client.post сам превращает объект в JSON. Пути пишутся без адреса сервера: клиент знает выбранный порт.

Запуск

mvn test
[INFO] Running com.example.blog.PostApiTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.753 s -- in com.example.blog.PostApiTest
[INFO] BUILD SUCCESS

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

Если ожидание не совпало с ответом, JUnit показывает оба значения. Например, тест ждёт 201, но забыли заголовок Authorization:

[ERROR] com.example.blog.PostApiTest.createNeedsLogin -- Time elapsed: 0.095 s <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <201> but was: <401>

Первым в assertEquals пишется ожидаемое значение, вторым — настоящее. Если перепутать порядок, сообщение будет вводить в заблуждение.

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

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