Unit testing a Maven build

2 minute read

A few weeks ago, I asked a question on StackOverflow: how can you test a Maven build?

EqualsVerifier a complex Maven build, and I want to have some automated checks that the jar files and pom files produced by the build actually contain what I expect them to contain.

For instance, I want to check for the presence of an Automatic-Module-Name entry in the manifest file. It’s a multi-release jar, so I also want to check that the proper class files exist inside of the jar file’s META-INF/versions directory. Finally, EqualsVerifier is published to Maven Central, so I also want to check that the produced pom file contains the dependencies the project needs, but that the produced pom file for the fat jar that I also publish, doesn’t contain these dependencies.

Unfortunately, it’s hard to google for this, because of the words I would use to describe this (“test”, “verify”) already have specific different meanings in Maven.

I got a nice response from Karl Heinz Marbaise, one of the Maven devs, who suggested I create an additional Maven submodule, use a plugin to copy the relevant artifacts to a directory, and go from there.

So I created the equalsverifier-release-verify submodule in the project and used copy-rename-maven-plugin to copy the files, as follows:

<plugin>
    <groupId>com.coderplus.maven.plugins</groupId>
    <artifactId>copy-rename-maven-plugin</artifactId>
    <version>${version.copy-rename-maven-plugin}</version>
    <executions>
        <execution>
            <id>copy-artifacts</id>
            <phase>compile</phase>
            <goals>
                <goal>copy</goal>
            </goals>
            <configuration>
                <fileSets>
                    <fileSet>
                        <sourceFile>${artifact.src.main}/.flattened-pom.xml</sourceFile>
                        <destinationFile>${artifact.dst}/equalsverifier-main.pom</destinationFile>
                    </fileSet>
                    <fileSet>
                        <sourceFile>${artifact.src.main}/equalsverifier-${project.version}.jar</sourceFile>
                        <destinationFile>${artifact.dst}/equalsverifier-main.jar</destinationFile>
                    </fileSet>
                    <!-- more fileSets here -->
                </fileSets>
            </configuration>
        </execution>
    </executions>
</plugin>

Now the poms and jars are in the src/test/resources folder, I can work with them. For the pom files, I used Java’s built-in XPath API, because it’s simple, and my needs are simple as well. But you can use whatever you want.

For the jar files, I used NIO to access their content as a FileSystem:

var filename = "myArtifactId.jar"; // or "flattened.pom"
var file = getClass().getClassLoader().getResource(filename);
var uri = URI.create("jar:" + file.toURI().toString());
FileSystem fs = FileSystems.newFileSystem(uri, Map.of());

Now I can get a list of files that exist in the jar:

var path = fs.getPath("/");
Set<String> filenames = StreamSupport
    .stream(walk.spliterator(), false)
    .map(Path::toString)
    .collect(Collectors.toSet());

Or read the content of a file to check the content of the manifest:

var path = fs.getPath("/META-INF/MANIFEST.MF");
var out = new ByteArrayOutputStream();
Files.copy(path, out);
String content = out.toString();
assertTrue(content.contains("Automatic-Module-Name: nl.jqno.equalsverifier"));

I can even check if files are compiled to the correct Java version:

var path = fs.getPath("/com/example/MyClass.class");
var out = new ByteArrayOutputStream();
Files.copy(path, out);
byte[] content = out.toByteArray();
var actualVersion = content[7]; // the major version of the class file is at this location
assertEquals(52, actualVersion); // 52 = Java 8

(Here is a description of Java’s class file format, including a list of Java major class file versions.)

Note that for this post, I didn’t bother handling exceptions or closing resources. Filling in those blanks is left as an exercise for you, dear reader 😉.

This was a pretty fun thing to play around with! If you want to see the full code, take a look at the equalsverifier-release-verify submodule on GitHub!