testcontainers-java: GenericContainer run from Jupiter tests shouldn't require JUnit 4.x library on runtime classpath

I tried out the JUnit 5 support using the following build script. I’d expect that TestContainers doesn’t require the JUnit 4.x library. As you can see in the build script below, the legacy dependency has been excluded.

apply plugin: 'java-library'

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
version = '1.0'

repositories {
    jcenter()
}

dependencies {
    def junitJupiterVersion = '5.3.1'
    testImplementation "org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion"
    testImplementation "org.junit.jupiter:junit-jupiter-params:$junitJupiterVersion"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion"
    testImplementation 'org.testcontainers:junit-jupiter:1.10.1'
}

// IMHO shouldn't be required
configurations.all {
   exclude group: 'junit', module: 'junit'
}

tasks.withType(Test) {
    useJUnitPlatform()
}

In my test case, I am creating a GenericContainer.

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
public class TestContainerJunit5Test {
    @Container
    private GenericContainer appContainer = new GenericContainer();

    @Test
    void tryItOut() {
        // do something
    }
}

At runtime I get the following exception. My guess is that GenericContainer still uses JUnit 4.x classes.

java.lang.NoClassDefFoundError: org/junit/rules/TestRule
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.getDeclaredFields0(Native Method)
	at java.lang.Class.privateGetDeclaredFields(Class.java:2583)
	at java.lang.Class.getDeclaredFields(Class.java:1916)
	at org.junit.platform.commons.util.ReflectionUtils.getDeclaredFields(ReflectionUtils.java:1106)
	at org.junit.platform.commons.util.ReflectionUtils.findAllFieldsInHierarchy(ReflectionUtils.java:886)
	at org.junit.platform.commons.util.ReflectionUtils.findFields(ReflectionUtils.java:874)
	at org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields(AnnotationUtils.java:320)
	at org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields(AnnotationUtils.java:297)
	at org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromFields(ExtensionUtils.java:92)
	at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.prepare(ClassTestDescriptor.java:154)
	at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.prepare(ClassTestDescriptor.java:74)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$0(NodeTestTask.java:80)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:80)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:66)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:110)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:92)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$100(JUnitPlatformTestClassProcessor.java:77)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:73)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
	at com.sun.proxy.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:131)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:155)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:137)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassNotFoundException: org.junit.rules.TestRule
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 80 more

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 125
  • Comments: 56 (19 by maintainers)

Commits related to this issue

Most upvoted comments

Here’s a workaround if no dependency on JUnit 4 is a must: create fake TestRule and Statement classes under your test root. Since GenericContainer doesn’t really use them unless run by JUnit 4, this hack works fine.

package org.junit.rules;

@SuppressWarnings("unused")
public interface TestRule {
}

and

package org.junit.runners.model;

@SuppressWarnings("unused")
public class Statement {
}

wow, that was a quick response 😃 we switched to Junit5 and are explicitly excluding Junit4 from all dependencies to make sure people don’t use it accidentally or out of habit

Any updates on this? It is really causing our team to struggle as there are junit 4 annotation but they don’t work

Hi @bmuschko,

We very much agree, and we do plan to remove the dependency, but it will be a breaking change and we will do it in Testcontainers 2.0. We were planning to start 2.0 earlier but discovered a few low hanging fruits (like the JUnit 5 extension, or OkHttp transport), this is why it got delayed. But now probably is a good time to finally focus on 2.0 😃

/cc @rnorth @kiview

let’s just acknowledge that testcontainers is a mediocre project where nobody cares about transitive dependencies or other minor things.

😦 Another release of testcontainers, and yet it’s not 2.0 😦 😦

let’s just acknowledge that testcontainers is a mediocre project where nobody cares about transitive dependencies or other minor things.

While above is a bit harsh, it is really regrettable that 5 years since there is absolutely no progress on this issue. TestContainers should be on version 5 by now, but still we are “afraid” to release version 2. Do not be afraid folks, let’s move on from this and on to version 2…3… 5…!

Hi @bmuschko,

We very much agree, and we do plan to remove the dependency, but it will be a breaking change and we will do it in Testcontainers 2.0. We were planning to start 2.0 earlier but discovered a few low hanging fruits (like the JUnit 5 extension, or OkHttp transport), this is why it got delayed. But now probably is a good time to finally focus on 2.0 😃

/cc @rnorth @kiview

More than four years have gone since that comment. Still there.

Up. Are there any upcoming news? We’re looking forward to get rid of junit4 completely in our company test dependencies. And the only one is testcontainers (which brings that legacy into our test class path).

P.S. I ❤️ testcontainers, this library helped to move testing to another level.

Same here folks, I hope this issue will be addressed soon. Migrated projects to the latest Spring Boot 2.4.2 where JUnit 4 is removed, so I hope we can get JUnit 5 support for testcontainers with JUnit 4 exluded.

@detouched
Thanks. I also needed to include another empty class for my WebDriverContainer.

package org.junit.rules;
public class ExternalResource {
}

Was getting the following exception:


java.lang.NoClassDefFoundError: org/junit/rules/ExternalResource

	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
	at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
...
	at org.testcontainers.containers.Network.<clinit>(Network.java:22)
	at org.testcontainers.containers.BrowserWebDriverContainer.configure(BrowserWebDriverContainer.java:158)
	at org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:305)
	at org.testcontainers.containers.GenericContainer.start(GenericContainer.java:300)

Guys come on, the Testcontainers team are working on this in their own free time, if you really want to expedite it, then you should help contribute to the project or get your company to sponsor them.

If not, if the issue is really critical to you, you can always use the workaround by @detouched here: https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-625044008

@pixelstuermer since this is not a major issue, atm we’re focusing on other, bigger ones.

That said, we do plan to decouple the core from JUnit 4 in 2.0 (with probably an experimental API in 1.x that will allow the migration path), but it will take a bit of time.

@void-spark If using Gradle, you can save your junior developers from themselves with

configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(module("junit:junit"))
            .using(module("io.quarkus:quarkus-junit4-mock:3.0.0.Final"))
            .because(
                "We don't want JUnit 4; but is an unneeded transitive of testcontainers. " +
                    "See https://github.com/testcontainers/testcontainers-java/issues/970"
            )
    }
}

Which will yield

+--- org.testcontainers:testcontainers -> 1.17.6
|    +--- junit:junit:4.13.2 -> io.quarkus:quarkus-junit4-mock:3.0.0.Final
|    +--- \\ other dependencies

Since the “mock” doesn’t contain all the common annotations/classes like @Test, @Before etc and only the handful of classes directly referred to by testcontainers, the completions are unlikely to be a problem.

And if you’re worried about what’s in it, can check out https://github.com/quarkusio/quarkus/tree/main/core/junit4-mock

Wish this could be resolved, testcontainers is the only part still pulling in Junit4 for us, it would be nice not having to tell junior developers anymore that half the test related annotations IntelliJ provides should not be used.

Since this requires some more fundamental changes to the existing codebase and architecture, we can’t give an ETA on this feature. @bsideup already outlined some aspects in this issue and further inquiries won’t expedite the development or release of this feature.

@pixelstuermer since this is not a major issue, atm we’re focusing on other, bigger ones.

That said, we do plan to decouple the core from JUnit 4 in 2.0 (with probably an experimental API in 1.x that will allow the migration path), but it will take a bit of time.

OK thanks. Nice to hear that you will keep an eye on this 👍

Quarkus users can use this dep https://mvnrepository.com/artifact/io.quarkus/quarkus-junit4-mock

Module with some empty JUnit4 classes to allow Testcontainers to run without needing to include JUnit4 on the class path

May I suggest creating a “junit4-workaround” module within testcontainers that could be easily added to any projects that may need it. I can submit a PR with this.

Given the amount of duplicate issues asking for the same thing, I think this issue can be considered major?

Ugh. That’s such an ugly hack 😃 It’s better to use Checkstyle, maven enforcer or ArchUnit for such things.

See https://stackoverflow.com/questions/61629824/preventing-the-use-of-junit4-libraries-in-a-project for some good answers

Thanks for sharing @cameronbraid ! Looks like there are no dependencies and just dummy classes based on https://github.com/quarkusio/quarkus/tree/main/core/junit4-mock so seems it would work fine for anyone to use it, even if not using Quarkus.

Basically https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-625044008 and https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-629479506. Exclude junit4 and add workaround module with the empty classes. Not a perfect solution, but I’ve been using this setup locally for some time without issues so far.

@gastaldi thank you! 😃 We are here for you, our amazing users ❤️

@evonier no updates since the last acknowledgement of the problem and the promise to target it for 2.x version.

That may compile but doesn’t actually work.

Ugh. That’s such an ugly hack 😃 It’s better to use Checkstyle, maven enforcer or ArchUnit for such things.

In one way yes, but the nice part about the hack is that you don’t even get @Test, @Before etc (as mentioned above) on your tests’ classpath => editors like IntelliJ won’t even attempt to suggest it to the user. In that way, it’s more of a zeroconf approach, which is kind of nice.

Hello @detouched and everyone else,

your workaround works fine for my gradle build. When I try to execute the tests with Intellij there is always coming the error below:

Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath: class org.testcontainers.containers.FailureDetectingExternalResource, unresolved supertypes: org.junit.rules.TestRule

Has anyone an idea how I can execute the tests with a testcontainer via IDE?

With best regards Henni

That happened to me when adding those classes as Kotlin code and fixed when added as Java code. I copied the following classes from JUnit 4 repository to my repository:

I had to add also org.awaitility:awaitility dependency.

please read that CVE before using the “security issues” argument

oh come on now 😃 I am talking about attack surface, not that one particular issue.

@gastaldi what makes it major? Does it break anything? Or blocks some usage?

Since GenericContainer depends on a class from JUnit 4, we can’t easily remove the dependency, meaning that we either need to work on 2.0 or introduce a new API (something that we considered) that won’t have said classes in the class hierarchy, so it can be excluded.

FWIW the current “problem” with JUnit 4 is that both JUnit 4 and JUnit 5 annotations are present and sometimes people use org.junit.Test instead of Jupiter’s (unless we miss some other major issues with having JUnit 4 in our dependencies)

There are workarounds (see above), there are codestyle rules that prevents the usage of org.junit.* annotations. Given that, I struggle to agree that the issue is major, and that we should spend our (currently very reduced due to personal reasons) time on it and not on other major issues/features.

WDYT?

I excluded JUnit 4 by adding the following to build.gradle

configurations {
    testCompile {
        exclude group: 'junit', module: 'junit'
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testRuntime {
        exclude group: 'junit', module: 'junit'
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

For reference, this has become an issue in the project I’m working on. Somehow, IntelliJ is picking up that the project has junit4 on the class path (?) and tries to load the junit-vintage-engine.

image

The workaround in my case was to add testFixturesApi "org.junit.vintage:junit-vintage-engine:5.9.0" to the Gradle config. It’s a bit of an ugly hack, but posting this here in case it helps someone else.

Upstream IntelliJ issue: https://youtrack.jetbrains.com/issue/IDEA-233706/Failed-to-resolve-junit-vintage-engine-4.12.9-when-trying-to-run-JUnit-5-test#focus=Comments-27-8188989.0-0

Hello @detouched and everyone else,

your workaround works fine for my gradle build. When I try to execute the tests with Intellij there is always coming the error below:

Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath: class org.testcontainers.containers.FailureDetectingExternalResource, unresolved supertypes: org.junit.rules.TestRule

Has anyone an idea how I can execute the tests with a testcontainer via IDE?

With best regards Henni

Thanks @bsideup !

Yeah, I was reading the outcome of that discussion about podman after commenting in this issue and I think this API is planned for Podman 3.0 but I haven’t tested yet.

BTW TestContainers is fantastic and you guys are doing an amazing job! Keep up the good work! 👍🏻

@gastaldi There are no plans to support Podman in 2.0 (unless you know something that I don’t, e.g. some generosity by Red Hat to join us as a Gold Sponsor and push the idea forward). As we previously communicated many times, Podman should provide an API that is compatible with Docker, as they claim they do.


My use case is simple: we are planning to use TestContainers to bootstrap databases when running tests with Quarkus.

Nice! Although I am afraid in this case even the new experimental API in 1.x won’t help as you would expose the Testcontainers dependency to your users (otherwise you would just use the stub workaround), and excluding junit4 would make the end users struggle to understand why new GenericContainer fails the compilation for them when used in Quarkus… 2.0 is closer than ever (yes, I know that I said something similar 2 years ago, lol), so this period of junit4 dependency shouldn’t be long 😃