So I just discovered by accident that my project h...
# plugin-development
n
So I just discovered by accident that my project had both junit 4 and junit 5 dependencies but was actually running using junit5 and the junit platform. I discovered this as I was confused why the
@Before
annotation had no effect (it's part of junit4). Is there a systematic way of preventing junit4 classes from creeping in to my junit5 project? And also vice versa?
v
I guess you would need a task that checks the test runtime class paths for presence of JUnit 4 and Jupiter. Of course you could use some test library that provides support for both and thus has a dependency on both. And generally it could of course be a valid situation, as you can use the Vintage engine to run JUnit 4 tests on JUnit platform. 🤷‍♂️
n
Thanks. Yeah it was unexpected that they were both there. I'll look in to the task solution but I was wondering if anyone else had something or if they have encountered it before.
t
Use dependency constraints or component selection rules to
reject
the "other JUnit"? (that way, you know where the dependency comes from and you can
exclude()
it from that specific dependency's transitives; you could also exclude the dependency at the configuration level but then no longer know where the dependency came from and whether maybe it could be needed, if only at runtime)
a
t
Well, except that they don't conflict, and can very well be used together (e.g. with JUnit Vintage as already mentioned) In my case, I use Google Truth for assertions, and it depends on JUnit 4. To make sure I won't use JUnit 4 classes myself, I exclude it from the Truth dependency in tests'
implementation
, and add Truth again as
runtimeOnly
without the exclusion (if I use Truth's
Assume
):
Copy code
// This is in a testing suite dependencies {} block
implementation(libs.google.truth) {
  exclude(group = "junit", module = "junit")
}
runtimeOnly(libs.google.truth) // bring junit:junit back
Ideally Truth would declare junit:junit as a runtimeOnly dependency, as it's not apparent anywhere in Truth's API, but it's built with Maven so…
a
Ideally Truth would declare junit:junit as a runtimeOnly dependency
Sounds like another request :) I believe the plugin could modify Truth to make that so
t
That wouldn't change the fact that I do have JUnit 4 and JUnit Jupiter in the runtime classpath and this is totally fine, even without JUnit Vintage 😉
a
what I was interested in was excluding JUnit 4 from compile time to prevent OP's accidental usage of JUnit's
@Before
t
Fwiw, following this discussion, I replaced my
exclude
and duplicate `implementation`/`runtimeOnly` declaration with the following in my convention plugin:
Copy code
jvmDependencyConflicts {
    patch {
        module("com.google.truth:truth") {
            reduceToRuntimeOnlyDependency("junit:junit")
        }
    }
}
I don't have many dependencies on the projects where I use this, so I didn't put anything in place to possibly fail the build if JUnit 4 somehow appears in the tests' compile classpath.
v
Use dependency constraints or component selection rules to
reject
the "other JUnit"?
Ah, yeah, better than check task of course. Use a component metadata rule to make Jupiter API and JUnit 4 have one capability in common, so that resolution fails if both end up in the same resolved configuration automatically. You can either do it manually or with the helpers from the
jvm-dependency-conflict-resolution
plugin.
sounds like a good feature request for https://github.com/gradlex-org/jvm-dependency-conflict-resolution
Definitely not, as there is no conflict. Both can be used at the same time in the same source set just fine as already mentioned, and that is at least for migration but maybe also for other reasons just fine and not a problem at all. It is quite unlikely that a test class is both, a JUnit 4 test as well as a a Jupiter test. But the plugin cannot help you with preventing to use classes from both in the same class or file. And it should definitely not prevent having both in the same classpath by default. Only on request in cases where someone does want to prevent it like @no might want to, and that it already provides as syntax sugar over Gradle API that he could also use directly. 🙂