Module has been rejected due to conflict with itself - trying to use java-test-fixtures plugin again

I have a build on Gradle 6.8 which is failing to run tests because it can’t resolve testRuntimeClasspath.

But the error looks like this:

Caused by: org.gradle.api.artifacts.ivyservice.DefaultLenientConfiguration$ArtifactResolveException: Could not resolve all task dependencies for configuration ':a:testRuntimeClasspath'.
Caused by: org.gradle.internal.resolve.ModuleVersionResolveException: Could not resolve project :b.
Required by:
    project :a
    project :a > :b
Caused by: org.gradle.api.GradleException: Module 'com.acme:b' has been rejected:
   Cannot select module with conflict on capability 'com.acme:b:1.2.3' also provided by [com.acme:b:1.2.3(runtimeElements), com.acme:b:1.2.3(tests)]

The dependencies in a look like this:

dependencies {
    implementation(project(":b"))
    testImplementation(project(path = ":b", configuration = "tests"))
}

Given that com.acme:b:1.2.3 and com.acme:b:1.2.3 are the same dependency, I’d first like to understand why this is considered a conflict.

I assume it’s something to do with the “tests” configuration somehow having a capability which matches the main configuration, but when I look in the metadata file for project b, the only thing with any capabilities at all is the testFixtures sections - in fact, the tests section doesn’t even have an entry in the metadata, so I don’t get why it would have capabilities in the first place.

The second thing I’d like to know is, how do we avoid this?

Additional background: This abuse of a “tests” configuration is something that’s common in our project to access test code from another project. I’m currently trying to switch to test fixtures, but am getting this error on the branch where I have added the java-text-fixtures plugin - before even moving any of the code. The project is too big to update all projects at the same time - doing so guarantees that the PR will never get through, because the team is large enough that someone is guaranteed to commit a conflict before the PR passes the build.

Investigation so far:

Hey,

The (..., configuration = "...") way to declare a dependency by referring to a ‘configuration’ in another project directly is rather outdated (IMO it should be deprecated). Instead you should use Attributes and/or Capabilities as concepts of the Variant Aware Dependency Management.

In your use case, you should use Capabilities. Which you may do indirectly either by using the java-test-fixtures plugin or java.registerFeature(...).

I explain this further in this video (example project on GitHub is linked in the description):

Hope this helps.

So what we have done for now at least is added an explicit outgoing capability for the “tests” configuration - a configuration which we’ll delete later, once everything is using testFixtures. It was just too many files to change in one PR to get it through in a single shot, and we wanted to get one module working before doing all the rest. (That one module currently seems to work but I’m giving it a week for people to notice any issues with it that the build didn’t detect.)