Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Tests: @QuarkusTest and RestAssured

Checking the API by hand with curl after every change is tiring. A test does the same automatically: it sends requests and compares the answers with the expected ones. In Quarkus a test annotated with @QuarkusTest starts the real application — with the database, the beans and the settings of the test profile — and the requests are conveniently written with the RestAssured library. The generator has already added both dependencies to pom.xml: quarkus-junit and rest-assured.

The first test

Tests live in src/test/java, in the same package as the code:

// 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("Test post", "Written by a test"))
                .when().post("/posts")
                .then()
                .statusCode(201)
                .body("id", notNullValue())
                .body("title", is("Test post"));
    }
 
    @Test
    void returns404ForUnknownPost() {
        given()
                .when().get("/posts/999")
                .then()
                .statusCode(404)
                .body("error", is("Post 999 not found"));
    }
}

The chain reads like a sentence: "given — when GET /posts — then the status is 200 and the body has at least two elements". body("title", is(...)) checks a field of the JSON response, size() the length of an array. RestAssured turns the NewPost object into JSON by itself.

Running

./mvnw test

Output shortened:

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

The application starts once for all tests, on port 8081, so it does not clash with dev mode on 8080. Dev Services again starts the database for the tests, and Startup fills it with two posts — that is why the greaterThanOrEqualTo(2) check passes.

Continuous testing

In a running quarkus:dev, press r. Quarkus runs the tests and from then on reruns them after every file save — only the ones the change affects. The result is shown at the bottom of the terminal and in the Continuous Testing section of the Dev UI.

Replacing the REST client

A test should not depend on someone else's service on the internet. A bean can be replaced with a mock, a stand-in object. Add the dependency:

<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", "anna@example.com"));
 
        given()
                .when().get("/authors/1")
                .then()
                .statusCode(200)
                .body("name", is("Anna"));
    }
}

@InjectMock replaces the real client with a mock in this test only, and Mockito.when(...).thenReturn(...) sets what it returns. No request goes to the internet.

Common mistake

If you forget .contentType(ContentType.JSON) in the createsPost test, it fails:

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

This is the same 415 Unsupported Media Type as with curl without the header: the server does not know that the body is JSON. The line number :31 points to the check that failed.

Thanks for reading!

Donate to the site — we work hard for you.