How to Test Quarkus applications

In this tutorial, we’ll have a look at writing tests using Quarkus applications. We’ll cover unit tests that you can run using JUnit Testing Framework and RESTAssured that simplifies the Test of REST Endpoints.

The @QuarkusTest annotation

You can Test Quarkus applications with any Java based testing framework. However, your testing can be greatly simplified by using JUnit 5 testing tool. Also, Quarkus is well integrated with REST-Assured testing framework for validating REST Endpoints.

REST Assured is a Java library that can be used to write powerful tests for REST APIs using a flexible Domain Specific Language (DSL).
The most important part of the Quarkus testing framework is the annotation io.quarkus.test.junit.QuarkusTest.
When you annotate a test class with this annotation, you are selecting that Test to be run according with Quarkus life cycle:

  1. The Quarkus application starts. When the application is ready to serve request, the Test execution will begin
  2. Each Test will execute against the running instance
  3. The Quarkus application stops.

Let’s see a practical example of how to test a REST Endpoint.

Endpoint Resource Testing

Firstly, we will be covering a simple Resource Endpoint test. This is a minimal REST Endpoint you can create using quarkus-resteasy :

@Path("/hero")

public class HeroEndpoint {
    @Inject HeroService service;
    
    @GET
    public List<Hero> list() {
        return service.getHeros();
    }
    
    @POST
    public Response create(Hero hero) {        
        service.add(hero);
        return Response.ok(hero).status(201).build();
    }
}

The HeroService Class stores the list of Hero objects in an ArrayList:

@ApplicationScoped

public class HeroService {
    ArrayList<Hero> heros = new ArrayList<Hero>( 
            Arrays.asList(new Hero("Bruce","Wayne"), new Hero("Peter","Parker")) );

    public ArrayList<Hero> getHeros() {
        return heros;
    }

    public void add(Hero hero) {
        getHeros().add(hero);
        
    }
}

Let’s write our first Test. In order to Test Quarkus applications you can use the io.quarkus.test.junit.QuarkusTest

@QuarkusTest
public class HeroEndpointTest {

    @Test 
    public void testSize() {
        given()
        .when().get("/hero")
        .then()
        .statusCode(200);

    }

    @Test
    public void testBody() {
        given()
        .when().get("/hero")
        .then()
        .statusCode(200)
        .body(
                containsString("\"name\":\"Bruce\",\"surname\":\"Wayne\""),
                containsString("\"name\":\"Peter\",\"surname\":\"Parker\""));
    }

    @Test
    public void testPost() {
        given()
        .body("{\"name\": \"Bruce\", \"surname\": \"Banner\"}")
        .header("Content-Type", "application/json")
        .when()
        .post("/hero")
        .then()
        .statusCode(201);

        given()
        .when().get("/hero")
        .then()
        .statusCode(200)
        .body("$.size()", is(3),
                "[0].name", is("Bruce"),
                "[0].surname", is("Wayne"),
                "[1].name", is("Peter"),
                "[1].surname", is("Parker"),
                "[2].name", is("Bruce"),
                "[2].surname", is("Banner"));
    }

}
  • The testSize method shows how you can test the status code of the Rest Endpoint (“/hero”)
  • The testBody method shows how to check the GET Response for “/hero” using the containsString method. That will check the actual body content
  • The testPost method shows how to send a POST with Restassured and to check the GET Response for “/hero” using another approach. In this case, we are fetching the list of JSON using the array id.

Running the Test

To build a Quarkus application using @QuarkusTest, you need to add the following dependency in your application:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

The above dependencies are automatically added when you scaffold a Quarkus application, for example with the Online Initializer. Besides, to configure JSON Serialization, you need to choose one of the available options, for example Jackson or JSON-B:

quarkus test application

To run the Test is sufficient to build and install the project:

mvn install

if your IDE supports it, you can also run it from it by running it as JUnit Test:

By default, Quarkus will use port 8081 for running HTTP Tests with a Timeout of 30 seconds for REST Assured tests. If you want to change these defaults, you can use the following configuration properties:

quarkus.http.test-port=8083
quarkus.http.test-timeout=10s

Adding @TestHTTPEndpoint to your Class

Our basic Test example includes the Endpoint Path as part of the test methods:

given()
 .when().get("/hero")
 .then()
 .statusCode(200);

You can decouple the Path of your Endpoint from your code by using the @TestHTTPEndpoint annotation. Example:

@QuarkusTest
@TestHTTPEndpoint(HeroEndpoint.class)

public class HeroEndpointTest {

    @Test 
    public void testSize() {

        given()
        .when().get()
        .then()
        .statusCode(200);

    }

 // Other test methods
}	

As you can see from the above example, your Test methods don’t include any more a reference to the Path. @QuarkusTest automatically retrieves it from the HeroEndpoint @Path

Testing an HTTP Resource of your Quarkus application

The next test example shows how to check the content of a Web resource using the @TestHTTPResource annotation. This annotation is bound to a java.net.URL resource. Therefore you can read the HTTP resource as an InputStream and verify the content of the page. In this example, we are checking that the HTML page contains the title “Quarkus-hibernate example”:

@QuarkusTest
public class CustomerEndpointHTTPTest {
    @TestHTTPResource("index.html")
    URL url;

    @Test
    public void testIndexHtml() throws Exception {
        try (InputStream in = url.openStream()) {
            String contents = readStream(in);
            Assertions.assertTrue(contents.contains("<title>Quarkus-hibernate example</title>"));
        }
    }

    private static String readStream(InputStream in) throws IOException {
        byte[] data = new byte[1024];
        int r;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        while ((r = in.read(data)) > 0) {
            out.write(data, 0, r);
        }
        return new String(out.toByteArray(), StandardCharsets.UTF_8);
    }
}

Adding Callbacks for each test

JUnit includes out of the box a set of annotations to fire callback methods before/after all tests are executed and before/after each Test execution:

class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

Besides, if you want to configure Test callbacks specifically for @QuarkusTest you can implement the  QuarkusTestBeforeEachCallback and QuarkusTestAfterEachCallback interface. See the following example:

import io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback;
import io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback;
import io.quarkus.test.junit.callback.QuarkusTestMethodContext;

public class TestCallBackExample implements QuarkusTestBeforeEachCallback, QuarkusTestAfterEachCallback {

    public void beforeEach(QuarkusTestMethodContext context) {
        System.out.println("Executing " + context.getTestMethod());
    }
    public void afterEach(QuarkusTestMethodContext context) {
        System.out.println("Executed " + context.getTestMethod());
    }
}

In order to register the CallBack classes, you have to add a file under the folder resources/META-INF/services with the name of the interface:

$ tree src/main/resources/
src/main/resources/
├── application.properties
├── import.sql
└── META-INF
    ├── resources
    │   └── index.html
    └── services
        ├── io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback
        └── io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback

Within each file, you need to specify the implementing class, which is in our example (for both files):

org.acme.TestCallBackExample

Continuous Testing

To expedite the development process Quarkus supports live coding so that code changes are automatically reflected in your running application. When running in dev mode, you can also combine live coding with continuous testing. This is pretty simple to do, just start your application in dev mode:

$ mvn quarkus:dev

You will see that the Console includes a set of additional options at the bottom:

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2022-05-09 09:19:03,796 INFO  [io.quarkus] (Quarkus Main Thread) basic-test 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.8.3.Final) started in 1.291s. Listening on: http://localhost:8080

2022-05-09 09:19:03,798 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2022-05-09 09:19:03,799 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx]

--
Tests paused
Press [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

If you type “r”, the Tests which are in your project will execute:

All 3 tests are passing (0 skipped), 3 tests were run in 2234ms. Tests completed at 09:23:36.

This allows to apply live changes to your code and re-test it, without stopping Quarkus! If you type “h” from the Console, a list of additional options will display to allow selective testing options:

[r] - Re-run all tests
[f] - Re-run failed tests
[b] - Toggle 'broken only' mode, where only failing tests are run (disabled)
[v] - Print failures from the last test run
[p] - Pause tests
[o] - Toggle test output (disabled)
[i] - Toggle instrumentation based reload (disabled)
[l] - Toggle live reload (enabled)
[s] - Force restart
[h] - Display this help
[q] - Quit

Using a specific profile for your Quarkus Tests

Out of the box Quarkus includes the following configuration profiles:

  • dev – Triggered when running in development mode (i.e. quarkus:dev)
  • test – Triggered when running tests
  • prod – This is picked up when not running in development or test mode

You can use the following syntax to bind a configuration parameter to a specific profile:

%{profile}.config.key=value

As an example, you can use the following application.properties configuration file, which defines multiple profiles in it:

%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgresDev
%test.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:6432/postgresTest
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:7432/postgresProd

In the above configuration, we have specified three different JDBC URLs for our Datasource connection. Each one is bound to a different profile. This way, you can use a configuration specific for the test profile.

Besides that, you can also define custom profiles by implementing the io.quarkus.test.junit.QuarkusTestProfile as in this example:

import io.quarkus.test.junit.QuarkusTestProfile;

public class CustomProfile implements  QuarkusTestProfile {

    @Override
    public Map<String, String> getConfigOverrides() {
        return Map.of("message", "Hi there!");
    }

    @Override
    public String getConfigProfile() {
        return "custom-profile";
    }
}

To use the custom profile in your tests, just add the @TestProfile with a reference to your Profile class:

import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
@TestHTTPEndpoint(CustomerResource.class)
@TestProfile(CustomProfile.class)
public class CustomProfileTest {

    @Test
    public void testCustomProfile() {

        given()
                .when().get()
                .then()
                .statusCode(200)
                .body(is("Hi there!"));


    }
}

Source code

The source code for this example is available: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/basic-test