Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GenericContainer run from Jupiter tests shouldn't require JUnit 4.x library on runtime classpath #970

Open
bmuschko opened this issue Nov 9, 2018 · 63 comments

Comments

@bmuschko
Copy link
Contributor

bmuschko commented Nov 9, 2018

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
@bsideup bsideup added the type/breaking-api-change Public APIs are being changed label Nov 9, 2018
@bsideup bsideup added this to the 2.0 milestone Nov 9, 2018
@bsideup
Copy link
Member

bsideup commented Nov 9, 2018

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

@kiview
Copy link
Member

kiview commented Nov 9, 2018

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

@bmuschko
Copy link
Contributor Author

bmuschko commented Nov 9, 2018

Great, thanks for confirming. Looking forward to 2.0.

@gosiapolitowska
Copy link

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

@kiview
Copy link
Member

kiview commented Jan 10, 2019

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

@gosiapolitowska
Copy link

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

@kiview
Copy link
Member

kiview commented Jan 10, 2019

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.

@evonier
Copy link

evonier commented Aug 5, 2019

Any updates on this one?

@bsideup
Copy link
Member

bsideup commented Aug 5, 2019

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

@DNAlchemist
Copy link

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

@eggilbert
Copy link
Contributor

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 🤔

@rfelgent
Copy link

rfelgent commented Mar 5, 2020

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.

@kiview
Copy link
Member

kiview commented Mar 5, 2020

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.

@kostapc
Copy link

kostapc commented Mar 5, 2020

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.

@detouched
Copy link

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

@gtiwari333
Copy link

gtiwari333 commented May 15, 2020

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

@pixelstuermer
Copy link

pixelstuermer commented May 18, 2020

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 👍 We also excluded all JUnit 4 dependencies to be forced to use Jupiter.

@bsideup
Copy link
Member

bsideup commented May 18, 2020

@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
Copy link

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

@lprimak
Copy link

lprimak commented May 27, 2023

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

@lprimak
Copy link

lprimak commented Oct 2, 2023

:( Another release of testcontainers, and yet it's not 2.0 :( :(

@perlun
Copy link
Contributor

perlun commented Oct 5, 2023

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

@void-spark
Copy link

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.

@chadlwilson
Copy link
Contributor

chadlwilson commented Oct 27, 2023

@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

@lprimak
Copy link

lprimak commented Oct 27, 2023

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

@perlun
Copy link
Contributor

perlun commented Oct 31, 2023

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.

@donalmurtagh
Copy link

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

@lprimak
Copy link

lprimak commented Dec 14, 2023

That may compile but doesn’t actually work.

@void-spark
Copy link

Nope, still no 2.0, any timelines yet? :)

@lprimak
Copy link

lprimak commented Apr 4, 2024

It's April 2024. It's time for 2.0 to fix this issue.This was opened in 2018, 6 years ago. Do you remember those days? No AI back then. No python. (oh wait, there was Python)

@AB-xdev
Copy link
Contributor

AB-xdev commented Apr 23, 2024

We also encountered this problem in our projects.

We couldn't find any junit4-mock dependency specifically for Testcontainers (besides the mock for quarkus mentioned above) so we simply wrote our own.
Feel free to use it :)

@artemptushkin
Copy link

This is one of the things that always will be with us I think, I can not image how many times I bumped into something "junit4 vs junit5" - go solve your dependencies. It's been 7 years since Junit5 was released but the ghost will be us for a long time.

Let's get rid from 4, legacy will be legacy

@erdi
Copy link

erdi commented May 15, 2024

I have to admit that I find it very surprising that this has not been addressed since Nov 2018 in a project with the impact, reach, success and motivation like Testcontainers.

@OleksandrShkurat
Copy link

OleksandrShkurat commented Jun 18, 2024

Seems the only working solution for projects working with Testcontainers and disallowing Junit4 to guarantee that developers won't use junit4 in their tests is to use IllegalImport check of CheckStyle (https://checkstyle.org/checks/imports/illegalimport.html) and ban both junit4 specific packages:

  • junit.*
  • org.junit.matchers.*
  • org.junit.rules.*
  • org.junit.runner.*
  • org.junit.runners.*
  • org.junit.validator.*

and separately specific classes:

  • org.junit.After
  • org.junit.AfterClass
  • org.junit.Assert
  • org.junit.Before
  • org.junit.BeforeClass
  • org.junit.Rule
  • org.junit.Test

@antonio-manuel
Copy link

In my case I opted by excluding all JUnit 4 dependencies and copy some of those required interfaces in the test sources.

Definitely I don't love it, but it seemed to me this was the best approach to ensure we only work with JUnit 5.

Screenshot 2024-06-18 at 15 01 51

Seems the only working solution for projects working with Testcontainers and disallowing Junit4 to guarantee that developers won't use junit4 in their tests is to use IllegalImport check of CheckStyle (https://checkstyle.org/checks/imports/illegalimport.html) and ban both junit4 specific packages:

  • junit.*
  • org.junit.matchers.*
  • org.junit.rules.*
  • org.junit.runner.*
  • org.junit.runners.*
  • org.junit.validator.*

and separately specific classes:

  • org.junit.After
  • org.junit.AfterClass
  • org.junit.Assert
  • org.junit.Before
  • org.junit.BeforeClass
  • org.junit.Rule
  • org.junit.Test

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

Successfully merging a pull request may close this issue.