JdbcTestUtils 和事务上下文测试

JdbcTestUtils and transaction context tests

我遇到了这个问题,导致我在 运行 使用事务上下文进行测试时无法使用 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

我克隆了你的项目,可以在我的机器上验证失败的测试。

测试断言中测试失败的原因 failingTestStoringNewObject:

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

是 JPA 的 EntityManger(Spring Data JPA 存储库在后台使用它)遵循后写策略并将对 JPA 实体的更改保存在其一级缓存中(在记忆中)。当您的代码执行 .save() 或使用 EntityManager.

的任何其他数据库操作时,并不总是会刷新对数据库的更改

EntityManger 尝试推迟刷新,因此尽可能晚地将其一级缓存与数据库同步。关于这方面有很多值得阅读的内容,我可以推荐 Vlad Mihalcea's guide on this

在你的例子中,当你用 @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 语句,而是调用以获取实体的主键。

如果您暂时在您的SimpleObjectFacade中使用repository.saveAndFlush(newObj).toDto();,您会发现它有效。

我会使用 SimpleObjectRepository 进行集成测试,并使用它的 .count() 方法。在这种情况下,基础 EntityManager 会识别对同一 table 的调用,并会在此之前刷新其当前更改,您将获得预期的结果。因为如果您使用 JdbcTemplate 则不会与 EntityManager 交互,因此 EntityManager 不会刷新它的更改,因为您使用 SELECT COUNT(0) ... 直接进入数据库。

更新:如果您不想进行任何调整并仍将 JdbcTemplate 与 Hibernate 和 JpaRepositories 结合使用,您 可以为您的测试设置以下冲洗模式:

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

这确保始终刷新持久性上下文