@MockBean, кажется, повторно запускает создание контекста и терпит неудачу после Migrate.sql

У меня есть два класса интеграционных тестов. Один из этих классов зависит от bean-компонента, который взаимодействует с внешним сервисом, поэтому мне нужно смоделировать этот bean-компонент, и @MockBean кажется идеальным для этого. Для ввода некоторых семян в БД я использую flyway's afterMigrate.sql. Итак, вот как это выглядит:

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
@Transactional
@Rollback
class FooTest {

  @Autowired
  private MyService myService;
}

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
@Transactional
@Rollback
class BarTest {

  @MockBean
  private ExternalService;

  @Autowired
  private MyService myService;
}

И afterMigrate.sql:

INSERT INTO my_table (id, name) VALUES (1, 'John Doe')

Проблема появилась, когда я аннотировал ExternatService как @MockBean, так как теперь afretMigrate.sql запускается дважды, и я получаю сообщение об ошибке:

java.lang.IllegalStateException: Failed to load ApplicationContext
....
Message    : ERROR: duplicate key value violates unique constraint "my_table_pkey"

Когда я меняю @MockBean на @Autowired, ошибка исчезает, и контекст создается без проблем. Также тесты проходят без проблем, если я запускаю BarTest отдельно. Это не ожидаемое поведение для @MockBean, как говорится в документации:

Any existing single bean of the same type defined in the context will be replaced by the mock. If no existing bean is defined a new one will be added. Dependencies that are known to the application context but are not beans (such as those registered directly) will not be found and a mocked bean will be added to the context alongside the existing dependency.

Это не говорит о том, что контекст будет воссоздан.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
0
2 010
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Потому что, когда вы используете аннотацию @MockBean, ваш контекст будет загружаться для каждого теста. См. эта проблема с гитхабом. Цитата с этой страницы:

The Spring test framework will cache an ApplicationContext whenever possible between test runs. In order to be cached, the context must have an exactly equivalent configuration. Whenever you use @MockBean, you are by definition changing the context configuration.

Поэтому, когда вы используете свой фиктивный компонент в разных тестах, контекст будет воссоздаваться каждый раз для вашего тестового класса. Итак, если у вас, например, есть компоненты, которые загружают данные в БД при создании контекста, например, компоненты для пролетного пути, они будут создаваться каждый раз при воссоздании контекста.

Ответ принят как подходящий

Вот как я решил эту проблему (которую я считаю проблемой).

Решение 1: Я создал класс MockConfig, который должен создать один mock для всего набора тестов:

@Configration
public class MockConfig {

  @Bean
  @Primary
  public ExternalService externalService() {
    return mock(ExternalService.class);
  }
}

И в тесте я просто автоматически подключаю внешнюю службу:

@Autowire
private ExternalService externalService;

Но у этого решения есть проблема: оно создаст настоящий bean-компонент, а затем переопределит его фиктивным bean-компонентом. Если ваша внешняя служба подключается к внешним ресурсам при создании, а вам это не нужно, вам понадобится другое решение.

Решение 2: Создайте базовый абстрактный класс с @MockBean в нем:

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
@Transactional
@Rollback
public abstract class BaseIntegrationTest {
  @MockBean
  ExternalService externalService;
}

И расширите интеграционный тест из этого базового класса:

class FooTest extends BaseIntegrationTest {

  @Autowired
  private MyService myService;
}

class BarTest extends BaseIntegrationTest {

  @Autowired
  private MyService myService;
}

Теперь контекст не будет обновляться, так как он всегда один и тот же, и настоящий компонент не будет создан.

Другие вопросы по теме