Site icon Craig Andrews

Testing a Java application on Windows without Windows

Java is supposed to be “write once, run anywhere” but in practice, there are always platform differences that can and do result in bugs. For that reason, and because in general it’s a good idea to test as much as possible, it’s nice to run tests (even for Java applications) on multiple platforms. As evidence of why it’s a good idea to do this testing, I ran into an obscure bug in Java that manifests as an issue in Spring when I ran the application on Windows – without a test, I would have never known until users reported it.

However, it can be challenging to run on Windows. For example, the project I’m working on is developed on a private GitLab instance that has plenty of Linux runners for CI/CD for running tests, but no Windows runners. However, the project will be used by users running Windows. The problem is immediately evident: how to run tests on Windows without any Windows systems?

The solution is to use a combination of Docker and Wine. Compile the project to a jar, copy the jar into a Docker container that includes the Windows version of Java running in Wine, then run tests against it. To support this approach, I’ve created and published a Docker image that runs the Windows version of AdoptOpenJDK 15 in Wine and I’ve published it to Docker Hub at craigandrews/wine-adoptopenjdk. This image can used to run any jar; for example, if you have a jar named my.jar, you can run it in the Windows version of Java with this command:

docker run -it --rm --name my-jar -v "$(pwd)":/usr/myjar -w /usr/myjar craigandrews/wine-adoptopenjdk:latest java -jar my.jar

For testing, I’m using Testcontainers to write a test that creates and starts the docker container. Here is such a test, written using JUnit 5:

import java.time.Duration;

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
import org.testcontainers.utility.MountableFile;

import static org.assertj.core.api.Assertions.assertThat;

/** Test that the jar is runnable on Windows by running it using java in wine.
 *
 * The build must have already run and produced a jar at {@code build/libs/project.jar} for this test to work.
 */
/* default */ class WindowsTest {
	@Test
	/* default */ void testHelp() throws Exception {
		try (GenericContainer<?> container = new GenericContainer<>("craigandrews/wine-adoptopenjdk")) {
			container
				.withCopyFileToContainer(MountableFile.forHostPath("build/libs/project.jar"), "this.jar")
				.withCommand("java -jar this.jar")
				.withStartupCheckStrategy(
						new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(20))
						).start();
			assertThat(container.getLogs()).describedAs("Testing that help text contains expected content").contains("Usage: java -jar ");
		}
	}
}

Before the test is run, the jar must already be built and exist at build/libs/project.jar which is the default path used by the Gradle build system. With this test in place, simply run ./gradlew build and the jar will be built then the test will be run.

I’ve provided a working demo of this approach at https://gitlab.com/candrews/test-java-using-wine-example This example demonstrates setting up Gradle, Testcontainers, and GitLab CI.

This approach isn’t without its downsides, however. Some such shortcomings include:

I’m finding this approach to be quite handy. Now I have confidence that my application will run on Windows.

Happy testing!

Testing a Java application on Windows without Windows by Craig Andrews is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Exit mobile version