Skip to content

3.6.0 regression: Spock specs abort discovery (Tests run: 0) when <excludedGroups> is set and JUnit 4 is on the test classpathΒ #3468

Description

@MenschNestor

Affected version

3.6.0 and 3.6.0-M1. Works with 3.5.6.

Bug description

Drafted with AI assistance. I built and ran the reproducer and verified the analysis myself.

A Spock specification aborts JUnit Platform discovery when <excludedGroups> is configured and the JUnit 4 jar is on the test classpath. The fork dies before any test runs, so the module reports Tests run: 0 β€” plain Jupiter tests in the same module do not run either.

Minimal reproducer: https://github.com/MenschNestor/surefire-spock-repro (one Spock feature, one Jupiter test)

mvn test -Dsurefire.version=3.5.6   # Tests run: 2, BUILD SUCCESS
mvn test -Dsurefire.version=3.6.0   # Tests run: 0, BUILD FAILURE
[INFO] --- surefire:3.6.0:test (default-test) @ surefire-spock-repro ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[ERROR] Could not find method with name [addition works] in class [com.example.ExampleSpec].
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[ERROR] There was an error in the forked process
[ERROR] org.apache.maven.surefire.booter.SurefireBooterForkException: There was an error in the forked process

All three conditions are jointly necessary:

surefire excludedGroups junit:junit on classpath tests run
3.5.6 slow yes 2
3.6.0 (empty) yes 2
3.6.0 slow no 2
3.6.0 slow yes 0, BUILD FAILURE
3.6.0-M1 slow yes 0, BUILD FAILURE

Cause

In JUnitPlatformProvider#newFilters, the exclude-category filter is added whenever org.junit.experimental.categories.Category is loadable:

if (!useTestNG) {
    Optional<Class<?>> categoryClass = getCategoryClass();
    if (categoryClass.isPresent()) {
        getPropertiesList(EXCLUDEDGROUPS_PROP)
                .map(strings -> getExcludeCategoryFilter(strings, categoryClass))
                .ifPresent(filters::add);
    }
}

Unlike the include branch just above it, this is not gated on JUNIT_VINTAGE_DETECTED, so having the JUnit 4 jar anywhere on the test classpath is enough β€” the Vintage engine does not have to be present, and in the reproducer it is not. The resulting PostDiscoveryFilter is applied to descriptors from every engine, and to read the @Category annotation it calls

methodSource.get().getJavaMethod()

in getExcludeCategoryFilter, and identically in getIncludeCategoryFilter.

Spock's MethodSource carries the feature name, not a JVM method name: Spock renames feature methods to $spock_feature_N_M and keeps the display name in @FeatureMetadata. In the reproducer the feature is def "addition works"(), so the reported name contains a space and can never resolve reflectively. MethodSource#getJavaMethod throws PreconditionViolationException, which escapes discovery and takes the fork down.

3.5.6 mapped groups/excludedGroups to TagFilter.includeTags/excludeTags only, with no reflection, so a MethodSource that does not correspond to a real method was harmless.

This is not specific to Spock. Any JUnit Platform engine whose MethodSource does not map one-to-one onto a reflective Method will hit it.

Possible fixes

Gating both category filters on JUNIT_VINTAGE_DETECTED would confine them to descriptors that can actually carry @Category. Independently, resolving the method defensively β€” treating an unresolvable MethodSource as "no category" rather than letting the exception escape β€” would stop a discovery-time reflection failure from killing the entire fork.

Impact

In our multi-module build this turns every module containing a Spock specification into Tests run: 0 plus a hard failure β€” five modules.

Activity

  1. self-assigned this
    on Oct 9, 2026
  2. added
    bugSomething isn't working
    on Oct 9, 2026
  3. olamy commented on Oct 9, 2026

    @olamy
    Member

    the repro pom contains

        <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter</artifactId>
          <version>5.14.1</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.13.2</version>
          <scope>test</scope>
        </dependency>
    

    is it on purpose?

  4. MenschNestor commented on Oct 12, 2026

    @MenschNestor
    Author

    the repro pom contains

                 <dependency>
                   <groupId>org.junit.jupiter</groupId>
                   <artifactId>junit-jupiter</artifactId>
                   <version>5.14.1</version>
                   <scope>test</scope>
                 </dependency>
                 <dependency>
                   <groupId>junit</groupId>
                   <artifactId>junit</artifactId>
                   <version>4.13.2</version>
                   <scope>test</scope>
                 </dependency>
    
        is it on purpose?
    

    Yes, we have a code base that's still transitioning from JUnit 4 to JUnit 5, so we use junit-vintage-engine to run the JUnit 4 tests, but that dependency is not in here because it was not needed for the repro.

    Thanks for the fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions