我遇到这个问题,导致我在使用事务上下文运行测试时不使用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实体的更改保存在其一级缓存(内存中(中。当代码使用EntityManager
执行.save()
或任何其他数据库操作时,并不总是刷新对数据库的更改。
EntityManger
试图推迟刷新,从而尽可能晚地将其一级缓存与数据库同步。关于这一点有很多值得阅读的地方,我可以推荐Vlad Mihalcea的指南。
在您的情况下,当您用@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
识别对同一个表的调用,并在此之前刷新其当前更改,您将得到预期的结果。因为如果使用JdbcTemplate
,则不会与EntityManager
交互,因此EntityManager
不会在使用SELECT COUNT(0) ...
直接访问数据库时刷新它的更改。
更新:如果您不想进行任何调整,并且仍然将JdbcTemplate
与Hibernate和JpaRepositories
结合使用,则可以仅为测试设置以下刷新模式:
spring.jpa.properties.org.hibernate.flushMode=ALWAYS
这确保了始终刷新持久性上下文