TestContainers made simple with JBang

This article discusses how to execute Tests on Container Images using JBang as Java scripting tool and TestContainers + JUnit 5 Jupiter as Test library.

Things we need to know

Modern software development relies on a diverse set of dependencies, services, and databases. Testing applications in a consistent, isolated, and reproducible environment can be challenging. This is where Testcontainers comes into play. Testcontainers is an open-source Java library that simplifies the integration testing of applications by providing lightweight, disposable containers for various dependencies. The following article provides a comprehensive introduction to TestContainers: Getting Started with Testcontainers for Java .

JBang is a tool that allows you to run Java programs directly from source files. It also simplifies the configuration and dependency management. To learn more about JBang, you can check this article: JBang: Create Java scripts like a pro

By combining TestContainers and JBang we can easily code scripts for testing container images without the need to set up a project for that.

Coding a simple Container Test

We will code a simple Testcontainers example that runs a Test on the Container image of Nginx Web Server: Let’s begin from the Container definition:

@Container
static GenericContainer container = new GenericContainer<>(DockerImageName.parse("nginx"))
            .withExposedPorts(80);
  • An Nginx container is defined with the GenericContainer class, specifying the Docker image “nginx.”
  • The container is configured to expose port 80, allowing external access to the Nginx server running inside the container.

Let’s see the full code:

///usr/bin/env jbang "$0" "$@" ; exit $? 

//JAVA 17

//DEPS org.testcontainers:testcontainers-bom:1.17.6@pom
//DEPS org.testcontainers:testcontainers
//DEPS org.testcontainers:junit-jupiter
//DEPS org.junit:junit-bom:5.9.1@pom
//DEPS org.junit.jupiter:junit-jupiter-api
//DEPS org.junit.jupiter:junit-jupiter-engine
//DEPS org.junit.platform:junit-platform-launcher
//DEPS org.junit.platform:junit-platform-reporting
//DEPS org.assertj:assertj-core:3.23.1

import com.github.dockerjava.api.model.ExposedPort;
import org.junit.jupiter.api.Test;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.LoggingListener;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

import static java.lang.System.out;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;

@Testcontainers
class testnginx {

	@Container
	static GenericContainer container = new GenericContainer<>(DockerImageName.parse("nginx"))
			.withExposedPorts(80);

	@Test
	void testNginxSaysWelcome() throws IOException, InterruptedException {
		var client = HttpClient.newBuilder()
				.version(HttpClient.Version.HTTP_2)
				.followRedirects(HttpClient.Redirect.NORMAL)
				.connectTimeout(Duration.ofSeconds(5))
				.build();

		var request = HttpRequest.newBuilder()
				.uri(URI.create(String.format("http://%s:%d/", container.getHost(), container.getFirstMappedPort())))
				.timeout(Duration.ofSeconds(5))
				.GET()
				.build();

		var response = client.send(request, HttpResponse.BodyHandlers.ofString());

		assertThat(response.statusCode()).isEqualTo(200);
		assertThat(response.body()).contains("Welcome to nginx!");
	}

	public static void main(final String... args) {
		var request = LauncherDiscoveryRequestBuilder.request()
				.selectors(selectClass(testnginx.class))
				.build();

		var launcher = LauncherFactory.create();
		var logListener = LoggingListener.forBiConsumer((t, m) -> {
			System.out.println(m.get());
			if (t != null) {
				t.printStackTrace();
			}
		});
		var execListener = new SummaryGeneratingListener();
		launcher.registerTestExecutionListeners(execListener, logListener);
		launcher.execute(request);
		execListener.getSummary().printTo(new java.io.PrintWriter(out));

		if (execListener.getSummary().getTotalFailureCount() > 0) {
			System.exit(1);
		}

		System.exit(0);
	}
}

In essence, this Test Class sends an HTTP Request on the Nginx Server. Then it verifies that the Welcome message matches with the default one.

Finally, it is worth mentioning that the main method uses the JUnit Platform Launcher API to gain more control over the test execution process. It allows the script to capture and log detailed information about the test execution, including individual test outcomes and any exceptions that may occur during the test run.

You can run it with:

jbang testnginx

Your Container will start right away:

testcontainer example

Then, check the output of the Test Report:

testcontainers with jbang tutorial

Conclusion

You have successfully run a Testcontainers-based test for an Nginx container using JBang. This approach allows you to easily experiment with Testcontainers tests without the need for a complex build setup. Feel free to customize the script or explore additional Testcontainers features based on your testing needs.