I have a simple goal: I want to be able to use the maven-failsafe-plugin, or any viable alternative, to run tests against a jar I'm building with the maven-shade-plugin. Specifically, I want to run the tests after shade runs because I want an integration test that validates shade's relocation didn't break the thing I'm trying to relocate as it often can.
As I'm trying to specifically relocate Jackson, it is important to make sure Jackson is still able to find annotations/etc. on certain POJOs so they (de)serialize correctly. Obviously, that works pre-relocation. We have ran into issues with the shaded jar not (de)serializing things correctly so it is important to us to have a test of some kind that can validate this behavior pre-deploy.
The issue I'm running into appears to be that the maven-failsafe-plugin is running the shaded jar in some capacity but testing the original source. Meaning, it is failing to load a class I relocated even though the relocation process in the maven-shade-plugin should've (and does in the live artifact) relocated the reference to that class.
What I expect: the maven-failsafe-plugin should run entirely off the shaded sources. If not, something else should allow me to run a similar test using the shaded/relocated code at build/CI time. E.g. as though I ran it from the command line. The following, btw, is the output I get from doing so:
>java -jar shade-integration-tests-1.0-SNAPSHOT.jar
{key=123}
test.shaded.com.fasterxml.jackson.databind.ObjectMapper
Exception I get from the test. Note, it is not an assertion failure but a Class loading failure (no other Exceptions occur):
Running test.shade.integration.tests.JsonIT
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.057 s <<< FAILURE! - in test.shade.integration.tests.JsonIT
test.shade.integration.tests.JsonIT.testClassName Time elapsed: 0.032 s <<< ERROR!
java.lang.NoSuchFieldError: MAPPER
at test.shade.integration.tests.JsonIT.testClassName(JsonIT.java:9)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
...
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>shade-integration-tests</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>test.shade.integration.tests.MainClass</Main-Class>
</manifestEntries>
</transformer>
</transformers>
<relocations>
<relocation>
<pattern>com.fasterxml.jackson</pattern>
<shadedPattern>test.shaded.com.fasterxml.jackson</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.0.0-M7</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.12.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.9.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
MainClass.java (can probably ignore this):
package test.shade.integration.tests;
import java.util.Map;
public class MainClass {
public static void main(String[] args) throws Exception {
Map<String, Object> result = Json.MAPPER.readValue("{\"key\":123}", Map.class);
System.out.println(result);
System.out.println(Json.MAPPER.getClass().getCanonicalName());
}
}
Json.java
package test.shade.integration.tests;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Json {
public static final ObjectMapper MAPPER = new ObjectMapper(); // This is part of the problem
private Json() {
}
}
JsonTest.java (maven-surefire-plugin, junit test that works fine):
package test.shade.integration.tests;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class JsonTest {
@Test
void testClassName() {
String result = Json.MAPPER.getClass().getCanonicalName();
assertEquals("com.fasterxml.jackson.databind.ObjectMapper", result);
}
}
JsonIT.java (maven-failsafe-plugin, IT that fails):
package test.shade.integration.tests;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class JsonIT {
@Test
void testClassName() {
String result = Json.MAPPER.getClass().getCanonicalName();
assertEquals("test.shaded.com.fasterxml.jackson.databind.ObjectMapper", result);
}
}