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

Controlling execution order of JUnit 5 extension relative to other extensions not possible #1017

Closed
jrehwaldt opened this issue Dec 18, 2018 · 5 comments

Comments

@jrehwaldt
Copy link

I am trying to use this extension together with @SpringBootTest. How to ensure testcontainer starts first before executing SpringBootTest extension?

This issue is not limited to Spring, but rather an issue as soon as multiple extensions are used at the same time and a certain ordering is required.

Context

From what I see in the JUnit 5 documentation the annotation order matters. Since Java sorts annotations in bytecode alphabetically Spring will always run first unless it is possible to explicitly import @ExtendWith(TestcontainersExtension.class). Currently, this cannot be done due to TestcontainersExtension being package-private.

Possible solution

I believe making TestcontainersExtension public is all that's needed for users to be able to explicitly define the order of multiple extensions.


Based on #887 (comment) and #887 (comment).

@michael-simons
Copy link
Contributor

Probably related to #1019.

jrehwaldt added a commit to jrehwaldt/testcontainers-gh1017 that referenced this issue Dec 21, 2018
jrehwaldt added a commit to jrehwaldt/testcontainers-gh1017 that referenced this issue Dec 21, 2018
@jrehwaldt
Copy link
Author

I created a reproducer in https://github.com/jrehwaldt/testcontainers-gh1017

The issue when using Kotlin is not the annotation order (maybe in Java it is as per JUnit documentation?), but instead that Spring will load the context very early in case of bean injection into the constructor.

Doesn't work:

@SpringBootApplication
class Application

@Component
class SomeSimpleBean

@Testcontainers // Has to run before SpringBootTest (SpringExtension) is run,
@SpringBootTest // but because of <constructorInjectedBean> below this doesn't happen.
@ContextConfiguration(initializers = [TestcontainersIssueGh1017ConstructorInjection.DynamoDbAwareInitializer::class])
class TestcontainersIssueGh1017ConstructorInjection(
    @Autowired private val constructorInjectedBean: SomeSimpleBean
) {

    companion object {
        @Container
        private val dynamodb = KGenericContainer("richnorth/dynalite:latest")
            .withExposedPorts(4567)
    }

    @Test
    fun `should execute TestcontainerExtension before SpringExtention`() {
        // When we reach here we're fine
    }

    internal class DynamoDbAwareInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
        override fun initialize(context: ConfigurableApplicationContext) {
            // FIXME Next line will fail with
            // > java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
            val dynamodbEndpoint = "http://${dynamodb.containerIpAddress}:${dynamodb.getMappedPort(4567)}"
            TestPropertyValues.of(
                "aws.dynamodb.endpoint: $dynamodbEndpoint",
                "aws.dynamodb.create-tables: true"
            ).applyTo(context)
        }
    }
}

Works as expected:

@Testcontainers // Runs before SpringBootTest as expected
@SpringBootTest // when using field injection.
@ContextConfiguration(initializers = [TestcontainersIssueGh1017FieldInjection.DynamoDbAwareInitializer::class])
class TestcontainersIssueGh1017FieldInjection {

    companion object {
        @Container
        private val dynamodb = KGenericContainer("richnorth/dynalite:latest")
            .withExposedPorts(4567)
    }

    @Autowired
    private lateinit var fieldInjectedBean: SomeSimpleBean

    @Test
    fun `should execute TestcontainerExtension before SpringExtention`() {
        // When we reach here we're fine
    }

    internal class DynamoDbAwareInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
        override fun initialize(context: ConfigurableApplicationContext) {
            val dynamodbEndpoint = "http://${dynamodb.containerIpAddress}:${dynamodb.getMappedPort(4567)}"
            Thread.sleep(1000)
            TestPropertyValues.of(
                "aws.dynamodb.endpoint: $dynamodbEndpoint"
            ).applyTo(context)
        }
    }
}

Changing the annotation order between @Testcontainers and @SpringBootTest correctly changes the invocation order of both extensions in Kotlin.

All four tests are green when running against #1020.

@kiview
Copy link
Member

kiview commented Dec 21, 2018

Thanks a lot for checking against #1020.
Let's try to merge it soon 🙂

@aguibert
Copy link
Contributor

@kiview @michael-simons I'm trying to write a new JUnit Jupiter extension that builds on top of the Testcontainers extension. The original solution proposed in this issue (make TestcontainersExtension a public class) is exactly I need, because then I can do:

@Target(TYPE)
@Retention(RUNTIME)
@ExtendWith({TestContainerExtension.class, MyExtension.class})
public @interface MyExt { }

I'm trying to follow the various issues linked in this issue to find out why the TestContainersExtension class was never made public, but didn't find anything. Is this option still on the table?

@22f
Copy link

22f commented Oct 30, 2020

@aguibert asked right question, @kiview @michael-simons why aren't TestcontainersExtension and related classes public?

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

No branches or pull requests

5 participants