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

Created on 9 Nov 2018  路  36Comments  路  Source: testcontainers/testcontainers-java

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
aretest frameworks resolutioacknowledged typbreaking-api-change

Most helpful comment

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

All 36 comments

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

Also see #87 with some more (historic) discussions regarding this topic.

Great, thanks for confirming. Looking forward to 2.0.

I ran into this issue as well and it's currently a blocker for me. Is there any estimated date when 2.0 will be released? Where can I find roadmap?

The product looks very promising btw :) looking forward to 2.0 as well so I can finally start using it

Sorry, we haven't updated the roadmap for quite some time :slightly_smiling_face:
Why exactly is it a blocker for you?

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

I see, makes sense for some projects.
We would love to remove this dependency rather sooner than later, however since it will be a breaking change, we haven't decided on when to actually do it yet.

Any updates on this one?

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

Updating testcontainers from version 1.11.4 to version 1.12.0 fixed the same problem for me

Updating testcontainers from version 1.11.4 to version 1.12.0 fixed the same problem for me

But things still derive from the JUnit 4 TestRule 馃

Is there any update ? With v 1.12.5 the exclusion of junit dependency still results in class problems.

As a hint: at my work, I use a test container jdbc url for creating the datasource. And therefore, the docker container "run" creation is delegated to the test container driver itself. No explicit coding with @ClassRule and/or @Rule within my unit/integration tests. From that point of view, the dependency to junit4 does not make really sense to me.

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

@rfelgent
As this issue is still open, this answer by @bsideup is still valid. The reasons are grounded in the way our code has been structured so far.

With v 1.12.5 the exclusion of junit dependency still results in class problems.

You can create your wrapper maven project, exclude JUnit4 and provide necessary JUnit classes separately.

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 {
}

@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)

Is there any intention to fix this in the near future?

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

Same here :+1: We also excluded all JUnit 4 dependencies to be forced to use Jupiter.

@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.

@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 馃憤

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

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.

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.

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

@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?

It introduces security issues, for one. For JUnit 5 projects, JUnit 4 dependency offers extra attack surface.

Hey @bsideup!

Since this introduces some API changes (major changes I believe) I agree this needs to be targeted to 2.0 (along with support for Podman, but that's another discussion 馃槃).

My use case is simple: we are planning to use TestContainers to bootstrap databases when running tests with Quarkus. Because our tests require JUnit 5, having the JUnit 4 JAR in the classpath may be confusing, but we can workaround it somehow.

See the full thread here: https://groups.google.com/g/quarkus-dev/c/EQpAI9kBrGk/m/URMR8OibBAAJ

@lprimak please read that CVE before using the "security issues" argument. Just having a jar on your classpath does not introduce any security issues. Plus, that CVE was about the TemporaryFolder rule, and too permissive access that allows _reading_ temporary files created by that rule by other users.
This is hilarious to refer to it as a security risk of having JUnit 4 as a 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.

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

@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 :)

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 thank you! :) We are here for you, our amazing users 鉂わ笍

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.

@svpace How do you imagine such a module to be implemented if Testcontainers core classes depend on JUnit4?

shade plugin?

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oneiros-de picture oneiros-de  路  3Comments

lovepoem picture lovepoem  路  3Comments

dabraham02124 picture dabraham02124  路  3Comments

naderghanbari picture naderghanbari  路  3Comments

richard77 picture richard77  路  3Comments