@SpringBootTest + @BeforeAll

У меня есть небольшое весеннее загрузочное приложение с базой данных и использованием rabbitmq. Поэтому я хотел бы протестировать интеграционный тест (H2 + apache qpid).

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

Поскольку мое приложение ожидает базу данных и mq, я использую @BeforeAll для его запуска:

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

Проблема в том, что мое веб-приложение запускается до того, как база данных/mq определена в @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);
    }
// ...

Веб-приложение запускается на этапе postProcessTestInstance, а методы @BeforeAll — на этапе 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);
}

См. пункты 1 и 2. Есть исполнения «подготовить» и «до».

Я не уверен, что это проблема junit, SpringExtension или я делаю что-то не так. Любой совет?

юнит-юпитер: 5.0.1

весенний тест: 5.0.0.RELEASE

весенняя загрузка-тест: 1.5.8.RELEASE


person Alex    schedule 27.10.2017    source источник
comment
Ваш метод с @BeforeAll должен быть статическим методом   -  person Yogi    schedule 27.10.2017
comment
Если вы зависите от того, когда SpringExtension создает контекст приложения, возможно, вам лучше реализовать Spring TestExecutionListener.   -  person Marc Philipp    schedule 29.10.2017


Ответы (4)


Оформить заказ https://www.testcontainers.org/ обеспечивает интеграцию с JUnit для запуска RabbitMQ и базы данных в Docker-контейнеры в рамках тестирования JUnit. Это делает интеграционные тесты более реалистичными, потому что вы используете те же версии базы данных и очереди сообщений, что и в рабочей среде.

person ams    schedule 15.03.2020

Это по дизайну, я думаю. Попробуйте добавить постпроцессор Bean/инициализатор контекста для инициализации/запуска вашей БД/rabbitMQ..

person Alexander.Furer    schedule 27.10.2017

Есть ли причина для запуска БД и брокера сообщений в тестовом классе? Мне кажется, что это неправильно по замыслу. Они оба должны запускаться вместе с контекстом вашего приложения, поскольку они являются частью вашей инфраструктуры.

Настройка инфраструктуры не входит в обязанности ваших тестов!

ИМХО, лучше сделать так:

  • Используйте зависимость H2 с областью действия test в maven + настройте стартер таким образом, чтобы он запускал H2 при запуске контекста приложения.
  • Запустите apache qpid (желательно встроенный) при запуске приложения
  • В @Before просто убедитесь, что вы очистили материал перед запуском тестового примера.
person Danylo Zatorsky    schedule 27.10.2017
comment
У меня есть H2 с тестовой областью. У меня Qpid как встроенный. Любые предложения, как запустить H2/Qpid до начала весенней загрузки? Я должен был использовать для этого BeforeAll, но похоже, что этот случай не работает. Использовать событие постпроцессора/контекста нельзя, потому что Bean с ConnectionFactory уже начал инициализацию. Я пытался играть с Lazy, но безуспешно. Так что если у вас есть реальный пример - поделитесь им со мной. - person Alex; 28.10.2017
comment
Да, проще всего было бы добавить @Order, другой способ — использовать стартеры. Для qpid вы можете использовать github.com/tabish121/qpid-jms-spring-boot (если у вас нет ограничений на сторонние библиотеки). Для H2 вы можете использовать что-то отсюда baeldung.com/spring-testing-separate- источник данных. Дело в том, что закваски всегда будут начинаться раньше ваших бобов, чего вы и хотите. - person Danylo Zatorsky; 28.10.2017

Аннотация JUnit 5 [@BeforeAll] заменяет аннотацию @BeforeClass в JUnit 4. Он используется, чтобы сигнализировать о том, что аннотированный метод должен выполняться перед всеми тестами в текущем тестовом классе.

@BeforeAll следует использовать в статическом методе

Подробнее:

  1. http://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
person Yogi    schedule 27.10.2017
comment
Да, так и должно быть. Хорошая точка зрения. В быстрых нет никаких различий для потока выполнения со статикой или без статики: метод @BeforeAll выполняется после запуска веб-приложения в обоих случаях. Также, как я вижу из источников junit, они просто ищут аннотированные методы. См.: github.com/junit-team/junit5/blob/master/junit-platform-commons/ строка 337. - person Alex; 27.10.2017