嵌套 JUnit 5 测试的 Spring DataJpaTest 事务行为

Nik*_*iou 5 java spring spring-data-jpa spring-boot junit5

我正在使用带有 JUnit 5 的 Spring Boot 2,为 JpaRepository 编写 DataJpaTest。测试针对真实的 PostgreSQL 数据库(非嵌入式,非内存)运行。

根据文档,默认情况下每个测试都在一个事务中并自动回滚。事实上,这就是我在不做任何额外努力的情况下得到的行为。这是我的测试最初的样子:

@ExtendWith(SpringExtension.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@DataJpaTest
class MyRepositoryIT {
    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private MyRepository myRepository;

    @Test
    void save() {
        // arrange
        MyEntity entity = new MyEntity().withName("Nikolaos");

        // act
        MyEntity saved = myRepository.save(entity);

        // assert
        assertThat(saved.getId()).isGreaterThan(0);
    }
Run Code Online (Sandbox Code Playgroud)

这按预期工作,实际上在测试输出中我可以看到它正在回滚事务:

20180822T085911 main INFO c.u.m.a.d.MyRepositoryIT Started MyRepositoryIT in 8.352 seconds (JVM running for 10.726)
20180822T085911 main INFO o.s.t.c.t.TransactionContext Began transaction (1) for test context [DefaultTestContext@1a8e9ed9 testClass = MyRepositoryIT, testInstance = acme.auth.db.MyRepositoryIT@68ea253b, testMethod = save@MyRepositoryIT, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@12fcb2c3 testClass = MyRepositoryIT, locations = '{}', classes = '{class acme.auth.Swagger2SpringBoot}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@57bd6a8f key = [org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@57d5872c, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6ee12bac, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@1b083826, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@3dc05093, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2d9d4f9d], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@6a6c7f42]; rollback [true]
Hibernate: insert into MyTable (firstname) values (?)
20180822T085912 main INFO o.h.h.i.QueryTranslatorFactoryInitiator HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select My0_.id as id1_4_, My0_.firstname as firstname2_4_ from My My0_
20180822T085912 main INFO o.s.t.c.t.TransactionContext Rolled back transaction for test: [DefaultTestContext@1a8e9ed9 testClass = MyRepositoryIT, testInstance = acme.auth.db.MyRepositoryIT@68ea253b, testMethod = save@MyRepositoryIT, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@12fcb2c3 testClass = MyRepositoryIT, locations = '{}', classes = '{class acme.auth.Swagger2SpringBoot}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@57bd6a8f key = [org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@57d5872c, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6ee12bac, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@1b083826, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@3dc05093, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2d9d4f9d], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]
Run Code Online (Sandbox Code Playgroud)

并且确实在数据库中,该表没有任何来自测试的剩余记录。

但是,我想使用 JUnit 5 嵌套测试,以便我可以构建我的测试前提条件。

当我像这样更改我的测试时,它不再回滚事务并且数据库被污染:

@ExtendWith(SpringExtension.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@DataJpaTest
class MyRepositoryIT {
    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private MyRepository myRepository;

    @Nested
    class WhenTableIsEmpty {    
        @Test
        void save() {
            // arrange
            MyEntity entity = new MyEntity().withName("Nikolaos");

            // act
            MyEntity saved = myRepository.save(entity);

            // assert
            assertThat(saved.getId()).isGreaterThan(0);
        }
    }
Run Code Online (Sandbox Code Playgroud)

测试也通过了,但它会在数据库中留下创建的记录。在测试输出中,根本没有提到事务这个词,所以我在第一个非嵌套测试中免费获得的事务机制似乎并没有在嵌套的情况下发挥作用。

有没有人有这种情况的经验?