Contents

Overview

Helidon provides built-in test support for Helidon testing with JUnit 5.

Maven Coordinates

To enable Helidon Testing Framework add the following dependency to your project’s pom.xml (see Managing Dependencies).

<dependency>
     <groupId>io.helidon.webserver.testing.junit5</groupId>
     <artifactId>helidon-webserver-testing-junit5</artifactId>
     <scope>test</scope>
</dependency>
Copied

Usage

Helidon provides a rich set of extensions based on JUnit 5 for Helidon WebServer testing. Testing can be done with automatic server start-up, configuration, and shutdown. Testing can also be done without full server start-up with DirectClient when no real sockets are created.

API

There are two main annotations that you can use to test Helidon WebServer.

  • @ServerTest is an integration test annotation that starts the server (opens ports) and provides client injection pre-configured for the server port(s).

  • @RoutingTest is a unit test annotation that does not start the server and does not open ports but provides a direct client (with the same API as the usual network client) to test routing.

The additional annotation @Socket can be used to qualify the injection of parameters into test constructors or methods, such as to obtain a client configured for the named socket.

The following table lists the supported types of parameters for the @SetUpRoute annotated methods. Such methods MUST be static and may have any name. The @SetUpRoute annotation has value with socket name (to customize the setup for a different socket).

  • Parameter type - supported class of a parameter

  • Annotation - which annotations support this parameter

  • Modules - which webserver extension modules support this signature

Parameters for the @SetUpRoute annotated methods.
Parameter TypeAnnotationModulesNotes
HttpRouting.Builder@ServerTest, @RoutingTest
HttpRules@ServerTest, @RoutingTestSame as HttpRouting.Builder, only routing setup
Router.RouterBuilder<?>@ServerTest, @RoutingTest
SocketListener.Builder@ServerTest
WebSocketRouting.Builder@ServerTest, @RoutingTestwebsocket

In addition, a static method annotated with @SetUpServer can be defined for @ServerTest, which has a single parameter of WebServerConfig.Builder.

The following table lists the injectable types (through constructor or method injection).

  • Type - type that can be injected

  • Socket - if checked, you can use the @Socket annotation to obtain a value specific to that named socket

  • Annotation - which annotations support this injection

  • Modules - which WebServer extension modules support this injection

  • Notes - additional details

Injectable types.
TypeSocket?AnnotationModulesNotes
WebServer@ServerTestServer instance (already started)
URIx@ServerTestURI pointing to a port of the webserver
SocketHttpClientx@ServerTestThis client allows you to send anything in order to test for bad requests or other issues.
Http1Clientx@ServerTest
DirectClientx@RoutingTestImplements Http1Client API
WsClientx@ServerTestwebsocket
DirectWsClientx@RoutingTestwebsocketImplements WsClient API

Extensions can enhance the features for the module helidon-testing-junit5-webserver to support additional protocols.

Examples

You can create the following test to validate that the server returns the correct response:

Basic Helidon test framework usage.
@ServerTest 
class MyServerTest {

    final Http1Client client;

    MyServerTest(Http1Client client) { 
        this.client = client;
    }

    @SetUpRoute 
    static void routing(HttpRouting.Builder builder) {
        Main.routing(builder);
    }

    @Test
    void testRootRoute() { 
        try (Http1ClientResponse response = client
                .get("/greet")
                .request()) { 
            assertThat(response.status(), is(Status.OK_200)); 
        }
    }
}
Copied
  • Use @ServerTest to trigger the testing framework.
  • Inject Http1Client for the test.
  • SetUp routing for the test.
  • Regular JUnit test method.
  • Call the client to obtain server response
  • Perform the necessary assertions.

To trigger the framework to start and configure the server, annotate the testing class with the @ServerTest annotation.

In this test, the Http1Client client is used, which means that the framework will create, configure, and inject this object as a parameter to the constructor.

To set up routing, a static method annotated with @SetUpRoute is present. The framework uses this method to inject the configured routing to the subject of testing – in the current case, the Quickstart application.

As everything above is performed by the testing framework, regular unit tests can be done. After completing all tests, the testing framework will shut down the server.

Routing Tests

If there is no need to set up and run a server, a DirectClient client can be used. It is a testing client that bypasses HTTP transport and directly invokes the router.

Routing test using @RoutingTest and DirectClient.
@RoutingTest 
class MyRoutingTest {

    final Http1Client client;

    MyRoutingTest(DirectClient client) { 
        this.client = client;
    }

    @SetUpRoute 
    static void routing(HttpRouting.Builder builder) {
        Main.routing(builder);
    }

    @Test
    void testRootRoute() { 
        try (Http1ClientResponse response = client
                .get("/greet")
                .request()) { 
            JsonObject json = response.as(JsonObject.class); 
            assertThat(json.getString("message"), is("Hello World!"));
        }
    }
}
Copied
  • Use @RoutingTest to trigger the testing framework.
  • Inject DirectClient for the test.
  • SetUp routing for the test.
  • A regular JUnit test method.
  • Call the client to obtain server response.
  • Perform the necessary assertions.

If only routing tests are required, this is a "lighter" way of testing because the framework will not configure and run the full Helidon server. This way, no real ports will be opened. All the communication will be done through DirectClient, which makes the tests very effective.

It is required to annotate the test class with the @RoutingTest annotation to trigger the server to do the configuration. Thus, it will inject the DirectClient client, which can then be used in unit tests.

Routing is configured the same way as in full server testing using the @SetUpRoute annotation.

Additional Information

WebSocket Testing

If WebSocket testing is required, there is an additional module for it. It is necessary to include the following Maven dependency to the Project’s pom file:

<dependency>
    <groupId>io.helidon.testing.junit5</groupId>
    <artifactId>helidon-testing-junit5-websocket</artifactId>
    <scope>test</scope>
</dependency>
Copied

WebSocket Testing Example

The WebSocket Testing extension adds support for routing configuration and injection of WebSocket related artifacts, such as WebSockets and DirectWsClient in Helidon unit tests.

WebSocket sample test.
@ServerTest
class WsSocketTest {

    static final ServerSideListener WS_LISTENER = new ServerSideListener();
    final WsClient wsClient; 

    WsSocketTest(WsClient wsClient) {
        this.wsClient = wsClient;
    }

    @SetUpRoute
    static void routing(WsRouting.Builder ws) { 
        ws.endpoint("/testWs", WS_LISTENER);
    }

    @Test
    void testWsEndpoint() { 
        ClientSideListener clientListener = new ClientSideListener();
        wsClient.connect("/testWs", clientListener); 
        assertThat(clientListener.message, is("ws")); 
    }
}
Copied
  • Declare WsClient and later inject it in the constructor.
  • Using @SetUpRoute, create WebSocket routing and assign a serverside listener.
  • Test the WebSocket endpoint using the regular @Test annotation.
  • Create and assign the clientside listener.
  • Check if the received message is correct.
ClientSideListener helper class.
static class ClientSideListener implements WsListener {
    volatile String message;
    volatile Throwable error;

    @Override
    public void onOpen(WsSession session) { 
        session.send("hello", true);
    }

    @Override
    public void onMessage(WsSession session, String text, boolean last) { 
        message = text;
        session.close(WsCloseCodes.NORMAL_CLOSE, "End");
    }

    @Override
    public void onError(WsSession session, Throwable t) { 
        error = t;
    }
}
Copied
  • Send "Hello" when a connection is opened.
  • Save the message when received and close the connection.
  • React on an error.

The WebSocket ClientSideListener is also a helper class that implements WsListener and is very straightforward:

ServerSideListener helper class.
static class ServerSideListener implements WsListener {
    volatile String message;

    @Override
    public void onMessage(WsSession session, String text, boolean last) { 
        message = text;
        session.send("ws", true);
    }
}
Copied
  • Send "ws" on a received message.

The testing class should be annotated with @RoutingTest only if routing tests are required without real port opening. Instead of WsClient, use DirectWsClient.

Reference