14

I have a small spring boot app with database and rabbitmq usages. So I would like to test with integration test (H2 + apache qpid).

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = TestSpringConfig.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)

As my app expect database and mq Im using @BeforeAll to start it:

@BeforeAll
public void before() {
    startMessageBroker();
    startDatabase();
}

The problem is that my web app starts before database/mq defined in @BeforeAll.

org.springframework.test.context.junit.jupiter.SpringExtension:

public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor,
        BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback,
        ParameterResolver {
// ...
    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        getTestContextManager(context).beforeTestClass();
    }
// ...
    @Override
    public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
        getTestContextManager(context).prepareTestInstance(testInstance);
    }
// ...

Web app starts in postProcessTestInstance phase and @BeforeAll methods in beforeAll.

org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor:

private void execute(TestDescriptor testDescriptor, C parentContext, ExecutionTracker tracker) {
    Node<C> node = asNode(testDescriptor);
    tracker.markExecuted(testDescriptor);

    C preparedContext;
    try {
        preparedContext = node.prepare(parentContext); // 1 <<<
        SkipResult skipResult = node.shouldBeSkipped(preparedContext);
        if (skipResult.isSkipped()) {
            this.listener.executionSkipped(testDescriptor, skipResult.getReason().orElse("<unknown>"));
            return;
        }
    }
    catch (Throwable throwable) {
        rethrowIfBlacklisted(throwable);
        // We call executionStarted first to comply with the contract of EngineExecutionListener
        this.listener.executionStarted(testDescriptor);
        this.listener.executionFinished(testDescriptor, TestExecutionResult.failed(throwable));
        return;
    }

    this.listener.executionStarted(testDescriptor);

    TestExecutionResult result = singleTestExecutor.executeSafely(() -> {
        C context = preparedContext;
        try {
            context = node.before(context); // 2 <<<

            C contextForDynamicChildren = context;
            context = node.execute(context, dynamicTestDescriptor -> {
                this.listener.dynamicTestRegistered(dynamicTestDescriptor);
                execute(dynamicTestDescriptor, contextForDynamicChildren, tracker);
            });

            C contextForStaticChildren = context;
            // @formatter:off
            testDescriptor.getChildren().stream()
                    .filter(child -> !tracker.wasAlreadyExecuted(child))
                    .forEach(child -> execute(child, contextForStaticChildren, tracker));
            // @formatter:on
        }
        finally {
            node.after(context);
        }
    });

    this.listener.executionFinished(testDescriptor, result);
}

See points 1 and 2. There are executions of 'prepare' and then 'before'.

Im not sure is it issue of junit, SpringExtension or Im doing something wrong. Any advice?

junit-jupiter: 5.0.1

spring-test: 5.0.0.RELEASE

spring-boot-test: 1.5.8.RELEASE

Alex
  • 967
  • 4
  • 15
  • 38
  • Your method with @BeforeAll should be static method – Yogi Oct 27 '17 at 16:45
  • 2
    If you depend on when the SpringExtension creates the application context, you might be better of implementing a Spring [`TestExecutionListener`](https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-tel-config). – Marc Philipp Oct 29 '17 at 12:43

4 Answers4

3

Checkout https://www.testcontainers.org/ it provides integration with JUnit to launch RabbitMQ and a database in docker containers as part of the JUnit testing. This makes integration tests much realistic because you using the same versions of database and message queue would be using in production.

ams
  • 60,316
  • 68
  • 200
  • 288
0

This is by design, I think. Try to add the Bean post-processor/Context initializer to init/start your DB/rabbitMQ..

Alexander.Furer
  • 1,817
  • 1
  • 16
  • 24
0

Is there any reason to start the DB and the message broker in test class? It seems to me that this is wrong by design. They both should be started along with your application context since they are part of your infrastructure.

It's not a responsibility of your tests to set up your infrastructure!

IMHO, a better way of doing things is the following:

  • Use H2 dependency with a test scope in maven + configure a starter in a way it starts H2 when the application context is starting
  • Start apache qpid (preferably embedded) on application start
  • In @Before just make sure you clean up stuff before running a test case
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Danylo Zatorsky
  • 5,856
  • 2
  • 25
  • 49
  • I have H2 with test scope. I have Qpid as embedded. Any suggestions how to start H2/Qpid before spring boot starts? I supposed to use BeforeAll for this but looks like this case is not working. Use post-processor/context event is not a way because Bean with ConnectionFactory already start initialization. I tried to play with Lazy but with no luck. So if you have some real example - please share it with me. – Alex Oct 27 '17 at 21:15
  • Yeah, the easiest way would be adding @Order, another way is to use starters. For qpid you could use https://github.com/tabish121/qpid-jms-spring-boot (if you do not have any restrictions on third-party libraries). For H2 you could use something from here http://www.baeldung.com/spring-testing-separate-data-source. The thing is that starters will always start before your beans, which is what you want here. – Danylo Zatorsky Oct 28 '17 at 08:46
-3

JUnit 5 [@BeforeAll] annotation is replacement of @BeforeClass annotation in JUnit 4. It is used to signal that the annotated method should be executed before all tests in the current test class.

@BeforeAll should be used in static method

For More reading:

  1. http://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
Yogi
  • 1,805
  • 13
  • 24
  • Yes, it should be. Good point. In fast there are no any differences for execution flow with static or without static: @BeforeAll method executed after web app starts in both cases. Also as I can see from the junit sources they just looking for annotated methods. See: https://github.com/junit-team/junit5/blob/master/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java line 337. – Alex Oct 27 '17 at 16:57