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
这确保始终刷新持久性上下文
我遇到了这个问题,导致我在 运行 使用事务上下文进行测试时无法使用 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
这确保始终刷新持久性上下文