Testing Helidon with TestNG

Dmitry Aleksandrov
Helidon
Published in
3 min readNov 2, 2022

With the release of Helidon 3.x, we have added support for TestNG. Now you are able to test not only with JUnit 5, but also with the latest TestNG. With Helidon 3.x, you can choose your preferred testing framework.

To use the TestNG integration just add the following dependency to your pom.xml:

<dependency>
<groupId>io.helidon.microprofile.tests</groupId>
<artifactId>helidon-microprofile-tests-testng</artifactId>
<scope>test</scope>
</dependency>

That’s it! You are ready to write you first TestNG tests.

TestNG follows the practices and annotations set established with JUnit 5 integration.

For example, if you want to test the Helidon QuickStart application, start by testing the /greet endpoint to make sure it returns the expected value.

First, create a test class:

@HelidonTest
class GreetTest {
@Inject
WebTarget webTarget;
@Test
void testDefaultGreeting() {
JsonObject jsonObject = webTarget.path("/greet")
.request()
.get(JsonObject.class);
String expected = "Hello World!";
String actual = jsonObject.getString("message");
assertEquals(expected, actual, "Message in JSON");
}
}

With the @HelidonTest annotation, we tell the Helidon TestNG integration to start the MicroProfile Helidon server before the first test, and then after the tests are complete, the server is stopped. The server will start on a random port. As the container is initialized, the tests will be executed against it, following the TestNG tests execution sequence.

Since we are testing the endpoints, a WebTarget is set up and configured against the current server, and can be injected, and used in our code. It is used to invoke the endpoint and validate the result

As with the JUnit extension, some additional tools for more sophisticated test execution are available for TestNG as well.

A new configuration property on class level can be defined with this annotation:

@AddConfig(key = "app.greeting", value = "Unite")

To define additional configuration from class path:

@Configuration(configSources = "test-config.properties")

Our example can be configured this way:

@AddConfig(key = "app.greeting", value = "Unite")
class GreetTest {
@Test
void testDefaultGreeting(WebTarget webTarget) {
validate(webTarget, "/greet", "Hello World!");
}
@Test
void testConfiguredGreeting(WebTarget webTarget) {
validate(webTarget, "/greet", "Unite World!");
}
private void validate(WebTarget webTarget,
String path,
String expected) {
JsonObject jsonObject = webTarget.path(path)
.request()
.get(JsonObject.class);
String actual = jsonObject.getString("message");
assertEquals(expected, actual, "Message in JSON");
}
}

To allow more sophisticated control over which bean CDI should load for a particular test to execute correctly, we can use @AddBean annotation:

@AddBean(TestBean.class)

This way TestBean will be added with ApplicationScoped scope set by default. The scope may be configured as a parameter of this annotation:

@AddBean(value = TestBean.class, scope = Dependent.class)

Following the same paradigm, we can add a CDI extension that is specific for our test with @AddExtension. Otherwise, we would need to create a META-INF/services record that would be picked up by every test class.

To add an extension to the container to modify its behavior:

@AddExtension(TestExtension.class)

For the most custom scenario, we can fully disable Bean discovery with the following annotation:

@DisableDiscovery

DisableDiscovery is mostly used in conjunction with AddBeans and/or AddExtension.

Here’s how it will look if we use all of the features described above:

@HelidonTest
@DisableDiscovery
@AddExtension(ConfigCdiExtension.class)
@AddBean(GreetTest.ConfiguredBean.class)
@AddConfig(key = "test.message", value = "Hello Blog!")
class GreetTest {
@Inject
ConfiguredBean bean;
@Test
void testBean() {
assertEquals("Hello Blog!", bean.message());
}
public static class ConfiguredBean {
@Inject
@ConfigProperty(name = "test.message")
private String message;
String message() {
return message;
}
}
}

Limitations of Helidon TestNG Integration

The TestNG integration in Helidon allows you to do almost everything you were able to do with JUnit. There are some limitations, however, which are come from the TestNG object management model and lifecycle.

For example, with TestNG tests you are unable to inject WebTarget in the individual tests. This means that constructions like:

@Test
void testSomething(WebTarget webTarget){ ... }

will not work. Otherwise, all other functionality works as expected!

Feedback

We would appreciate your feedback about this feature. Contact us:

--

--