【问题标题】:JdbcTestUtils and transaction context testsJdbcTestUtils 和事务上下文测试
【发布时间】:2020-07-11 18:37:17
【问题描述】:

我有这个问题导致我在使用事务上下文运行测试时不使用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

【问题讨论】:

  • 您可以在此处添加您正在测试的实际应用代码吗?断言也不应该是表中的两条记录吗?你从一个开始,在测试期间添加一个?
  • 你说得对,应该是描述中所说的2。生产代码只是存储单个新记录的简单方法。我正在使用 rest api 访问它,但这应该没有什么不同。该功能按预期工作,但我需要以不同的方式测试添加(而不是通过 JdbcTestUtils),因为我希望在测试完成后回滚更改。如果您需要一个示例 repo,我可以创建一个,但我看不出它有任何意义。这不是我第一次遇到这个问题。
  • 不过,很高兴看到 简单方法 是什么。所以你的基本问题是你不能用JdbcTemplate为测试准备测试数据还是不同?
  • github.com/Marek00Malik/JdbcTestUtils-sample 手头问题的示例。如果我将在 JdbcTestUtil 之前查询数据库,则一项测试有效。
  • 请注意,我知道使用 saveAndFlush,但此解决方案增加了额外的性能,因为您需要将 jpa 上下文刷新到磁盘。因此,如果可以使用传统的保存方法,建议使用此方法。

标签: java spring spring-data-jpa spring-test


【解决方案1】:

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

测试断言中测试失败的原因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 识别对同一个表的调用,并在此之前刷新其当前更改,您将获得预期的结果。因为如果您使用JdbcTemplate,则与EntityManager 没有交互,因此EntityManager 不会在您使用SELECT COUNT(0) ... 直接进入数据库时​​刷新它。

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

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

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

【讨论】:

  • 谢谢 Rieckpil。这是我已经知道的事情,我目前正在使用我的生产存储库的 TestImplementation,所以我可以在我的测试代码的其他地方使用它。如前所述,这是一种解决方法,我非常不想这样做,因为有时我需要编写特定于测试用例而不是一般存储库的 JPA 查询。
  • 另外,关于 JdbcTestUtil 使用的 JdbcTemplate 的注释:“一个 JdbcTemplate,用于执行 SQL 语句来查询数据库。您可以使用此类查询在执行数据库之前和之后确认数据库状态-相关的应用程序代码,而 Spring 确保此类查询在与应用程序代码相同的事务范围内运行。与 ORM 工具结合使用时,请务必避免误报。“这就是为什么我希望 JdbcTestUtil 会在与测试代码相同的事务。
  • 引用的最后一句话概括了问题与 ORM 工具结合使用时,请务必避免误报。。我已经用解决方法更新了我的答案。
  • 是的,这可能就是它的原因。仍然很奇怪,我想为了测试 Spring 在单个测试会话配置中运行 @Transactional 测试,这样当您使用 JdbcTestUtil 时,您将获得相同的事务。但也许这种方式更“通用”。谁知道。感谢 Rieckpill 的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-08
  • 2011-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多