JdbcTestUtils и тесты контекста транзакции - PullRequest
2 голосов
/ 11 июля 2020

У меня есть эта проблема, из-за которой я не использую JdbcTestUtils при запуске тестов с транзакционным контекстом.

Если я заключу свой тест в аннотацию @Transactional и использую JdbcTemplate/DataSource, это будет выглядеть так: транзакции, используемые в производственном коде и JdbcTestUtils, не совпадают, поэтому, если я запрашиваю базу данных в разделе then, утверждение не выполняется.

Это псевдотест:

@SpringBootTest
class TestClass {

  @Autowired
  private WebApplicationContext context;

  @BeforeEach
  void setup() {
    RestAssuredMockMvc.webAppContextSetup(context, springSecurity());
  }

  @Test
  @Transactional
  @DisplayName("test1")
  void test1(@Autowired DataSource dataSource) {
    //given
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    assertThat(countRowsInTable(jdbcTemplate, "some_tbl")).isEqualTo(1);

    //when
    // Execute app code here that adds a record to some_tbl

    //then
    assertThat(countRowsInTable(jdbcTemplate, "some_tbl")).isEqualTo(2); //<- Assertion fails. 
  }

В качестве обходного пути мне нужно использовать контекстные тестовые репозитории Spring для получения данных в тесте, но это кажется плохой идеей, и мне нужно поддерживать эти репозитории.

Ниже вы найдете простой spring -boot, чтобы показать проблему. https://github.com/Marek00Malik/JdbcTestUtils-sample

1 Ответ

1 голос
/ 20 июля 2020

Я клонировал ваш проект и смог проверить неудачный тест на своей машине.

Причина вашего провала теста в утверждении теста failingTestStoringNewObject:

assertThat(countRowsInTable(jdbcTemplate, "sample_table")).isEqualTo(1);

заключается в том, что EntityManger JPA (репозитории Spring Data JPA используют это под капотом) следует стратегии обратной записи и сохраняет изменения сущностей JPA в своем кэше первого уровня (в памяти). Сброс изменений в базе данных не всегда происходит, когда ваш код выполняет .save() или любую другую операцию с базой данных с использованием EntityManager.

EntityManger пытается отложить очистку и, следовательно, синхронизировать свой кеш первого уровня с базой данных как можно позже. Об этом можно много прочитать, и я могу порекомендовать руководство Влада Михалчи по этому .

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

2020-07-20 09:59:57.754  INFO 12648 --- [    Test worker] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@2b8ade2d testClass = SimpleObjectResourceHttpTest, testInstance = pl.code.house.example.jdbc.template.jdbctemplatetest.SimpleObjectResourceHttpTest@

Для большей наглядности вы также можете включить SQL ведение журнала для ваших тестов с помощью

spring:
  jpa:
    show-sql: on

. видите, что на самом деле не выполняется инструкция INSERT, а выполняется вызов для получения первичного ключа вашей сущности.

Если вы временно используете repository.saveAndFlush(newObj).toDto(); внутри SimpleObjectFacade, вы Посмотрим, что это работает.

Я бы использовал SimpleObjectRepository для вашего интеграционного теста и использовал его метод .count(). В этом случае базовый EntityManager распознает вызов той же таблицы и перед этим передаст sh свои текущие изменения, и вы получите ожидаемый результат. Потому что, если вы используете JdbcTemplate, взаимодействие с EntityManager отсутствует, и, следовательно, EntityManager не грипп sh, он изменяется, когда вы go непосредственно в базу данных с SELECT COUNT(0) ....

ОБНОВЛЕНИЕ : если вы не хотите вносить какие-либо изменения и по-прежнему используете JdbcTemplate в сочетании с Hibernate и JpaRepositories, вы можете установить следующий грипп sh режим только для ваших тестов:

spring.jpa.properties.org.hibernate.flushMode=ALWAYS

Это гарантирует, что всегда грипп sh контекст постоянства

...