I have a unit test written using the Spock Testing framework to test my Spring Boot application. I've declared the following test dependencies:
spock-spring
: 2.3-groovy-4.0spring-boot-starter-test
: 3.2.4surefire plugin version
: 3.2.2When I run mvn test
, the surefire plugin fails to execute them, giving the error:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.2.2:test (default-test) on project myspockspringtest:
[ERROR]
[ERROR] Please refer to C:\data\ myspockspringtest\target\surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
There was an error in the forked process
'java.util.Set org.junit.platform.engine.TestDescriptor.getAncestors()'
I followed surefire's instructions and looked at the surefire dump file it created, and see the error:
java.lang.NoSuchMethodError: 'java.util.Set org.junit.platform.engine.TestDescriptor.getAncestors()'
at org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener.getTestClassNames(StackTracePruningEngineExecutionListener.java:50)
I tried declaring Spock's bill of materials, spock-bom
to ensure Spock's dependencies are consistent, but this didn't help:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-bom</artifactId>
<version>2.3-groovy-4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
I've also tested without the spock-bom
declared, but still get this error.
How can I fix this and why is it happening?
The class in the exception, TestDescriptor
, comes from the org.junit.platform.engine
package.
Let's run mvn dependency:tree
(or the Maven Helper plugin, if using IntelliJ) to see if are multiple versions of org.junit.platform group components for the two dependencies we declared:
[INFO] --- dependency:3.6.1:tree (default-cli) @ myspockspringtest ---
...
[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.10.2:test
[INFO] | | +- org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test
[INFO] | | | +- org.opentest4j:opentest4j:jar:1.3.0:test
[INFO] | | | +- org.junit.platform:junit-platform-commons:jar:1.10.2:test
[INFO] | | | \- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO] | | +- org.junit.jupiter:junit-jupiter-params:jar:5.10.2:test
[INFO] | | \- org.junit.jupiter:junit-jupiter-engine:jar:5.10.2:test
...
[INFO] +- org.spockframework:spock-spring:jar:2.3-groovy-4.0:test
[INFO] | +- org.spockframework:spock-core:jar:2.3-groovy-4.0:test
[INFO] | | \- org.junit.platform:junit-platform-engine:jar:1.9.0:test
From this we can see that spock-spring
the junit-platform-engine
:
org.junit.platform:junit-platform-engine:jar:1.9.0:test
Looking at spring-boot-starter-test
it contains a similar-looking 1.x version 1.10.2
:
org.junit.platform:junit-platform-commons:jar:1.10.2:test
So maybe these two are conflicting.
We can also use Ctrl+N
in IntelliJ to look up TestDescriptor
to see which artifact it comes from. This tells us that TestDescriptor
lives in the junit-platform-engine-1.9.0
jar.
Where there is a conflict, Maven maps out the dependencies in a tree and chooses the dependency with the least number of hops to reach it. If there's still a conflict, Maven will choose the one declared first in the pom.
In this case, the junit-platform-engine
from spock-spring is nearer in the hierarchy to the root (3 hops for spock-spring vs 4 hops for spring-boot-starter-test's), so Maven picked Spock's 1.9.0 dependency for junit-platform-engine
and
So we have the following mismatch causing the 1.9.0 engine to find a method that is no longer in 1.10.2:
We have three options:
This issue was resolved in Spock 2.4-M1 so our best option is to use a version of Spock that's 2.4-M1 or later (thanks @leonard). At the time of writing, the latest 2.4.x version was:
So, we update our spock-spring
dependency to:
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>2.4-M4-groovy-4.0</version>
<scope>test</scope>
</dependency>
Our other two options will work for @ContextConfiguration
tests but not for @SpringBootTest
tests. We need Spock 2.4 (above) for the correct resolution.
Or
We can exclude the junit-platform-engine
dependency from spock-spring so that only the one in spring-boot-starter-test is available:
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
Or
We can add the spring-boot-dependencies
bom to the dependencyManagement
in our pom:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
This works both with and without the Spock BOM.
Once we've done this, our mvn dependency:tree
for these components now looks like:
[INFO] --- dependency:3.6.1:tree (default-cli) @ myspockspringtest ---
...
[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.10.2:test
[INFO] | | +- org.junit.jupiter:junit-jupiter-api:jar:5.10.2:test
[INFO] | | | +- org.opentest4j:opentest4j:jar:1.3.0:test
[INFO] | | | +- org.junit.platform:junit-platform-commons:jar:1.10.2:test
[INFO] | | | \- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO] | | +- org.junit.jupiter:junit-jupiter-params:jar:5.10.2:test
[INFO] | | \- org.junit.jupiter:junit-jupiter-engine:jar:5.10.2:test
[INFO] | | \- org.junit.platform:junit-platform-engine:jar:1.10.2:test
...
[INFO] +- org.spockframework:spock-spring:jar:2.3-groovy-4.0:test
[INFO] | +- org.spockframework:spock-core:jar:2.3-groovy-4.0:test
[INFO] | \- org.apache.groovy:groovy:jar:4.0.4:test
...
From this, we can conclude that the TestDescriptor.getAncestors() method in junit-platform's v1.9 was removed in v1.10.
To resolve it, we ensure that junit's dependencies are all in sync either by excluding them from our dependencies or by importing a bill-of-materials that declares them all. Or better still, using Spock 2.4 which resolve the issue completely.