Junit Spring 避免加载两次应用程序上下文数据源

Junit Spring avoid to load twice application context datasource

我有这个配置classes:

@ComponentScan(
        basePackages = { 
                "mypackage.controller",
                "mypackage.service",
                "mypackage.repository" 
        }
)
@TestPropertySource(locations="classpath:configuration.properties")
@Import({
    H2Configuration.class
})
public class TestConfiguration {
}

@Configuration
public class H2Configuration {

    @Bean
    public DataSource dataSource() throws SQLException {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        EmbeddedDatabase db = builder
                .setType(EmbeddedDatabaseType.H2)
                .addScript("h2/create.sql")
                .addScript("h2/insert.sql")
                .build();
        db.getConnection().setAutoCommit(false);
        return db;
    }

}

我有两个 class 测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes = { TestConfiguration.class })
public class FirstRepositoryTest {

    @Autowired
    MyFirstRepositoryImpl repository;

    @Before
    public void initTest() {
    }

    @Test(expected = NullPointerException.class)
    public void testNullRecords() {
        repository.foo(null, null);
    }
}


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes = { TestConfiguration.class })
public class SecondRepositoryTest {

    @Autowired
    MySecondRepositoryImpl repository;

    @Before
    public void initTest() {
    }

    @Test(expected = NullPointerException.class)
    public void testSomethingNullRecords() {
        repository.something(null, null);
    }
}

如果我 运行 每个 class junit 测试一次,一切顺利。

在全新安装阶段测试失败,因为应用程序上下文被初始化了两次。

例如,它尝试创建 h2 表两次并执行 insert.sql 脚本两次。

为了初始化 h2 数据库和应用程序上下文,我必须做些什么?

谢谢

因此失败的原因是当您 运行 作为 clean/install 的一部分进行测试时,数据库 (H2) 驻留在内存中。 create/insert 脚本已在第一个测试 运行 后执行。在此之后的任何后续测试执行都将导致重新执行相同的脚本,并且会发生错误。

使用 DROP TABLE IF EXISTS <table name>; 更新您的创建脚本。这将确保 table 被删除然后重新创建。

注意:我不确定您为什么明确指定 AnnotationConfigContextLoader。我认为,如果没有它,运行ner SpringJUnit4ClassRunner 将缓存未更改的上下文。不过我不知道这里是否属于这种情况。

我认为您可以开始查看有关 Integration Testing 的 Spring 文档。

将事务测试用于集成测试 (@Transactional) 也是一种很好的做法,它会在每个测试结束时回滚:请参阅 Transaction Management

为了避免为每个测试 class 重新创建 ApplicationContext 的成本,可以按照此处的说明使用缓存:Context Caching

对于 嵌入式数据库 的集成测试,您还可以找到文档:Testing Data Access Logic with an Embedded Database。 来自之前 link 的注释,与您的用例相匹配:

However, if you wish to create an embedded database that is shared within a test suite, consider using the Spring TestContext Framework and configuring the embedded database as a bean in the Spring ApplicationContext as described in Creating an Embedded Database by Using Spring XML and Creating an Embedded Database Programmatically.

希望您能找到一些有用的参考资料。

在单元测试中,您必须保证每个测试都是可重复的,并且与上下文无关。因此,只加载一次上下文并不是一个好主意。最好在执行后重新设置。为此,您可以在测试中使用 @DirtiesContext(classMode = ClassMode.AFTER_CLASS) classes

所以你将在下一个 junit class 启动时强制你的上下文重新启动

我从 Spring Boot 文档中找到的另一个好技巧 Embedded Database Support :

他们说:

If you are using this feature in your tests, you may notice that the same database is reused by your whole test suite regardless of the number of application contexts that you use. If you want to make sure that each context has a separate embedded database, you should set spring.datasource.generate-unique-name to true.

因此,要使每个 EmbeddedDatabase 独一无二,您可以尝试创建它们:

EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
                      .generateUniqueName(true)
                      ...
                      .build();