如何在Spring中每次测试前re-create数据库?
How to re-create database before each test in Spring?
我的 Spring-Boot-Mvc-Web 应用程序在 application.properties
文件中具有以下数据库配置:
spring.datasource.url=jdbc:h2:tcp://localhost/~/pdk
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
这是我做的唯一配置。我在任何地方都没有进行任何其他配置。尽管如此,Spring 和子系统会在每个 Web 应用程序 运行 上自动重新创建数据库。数据库在系统 运行 上重新创建,而它包含应用程序结束后的数据。
我不理解这个默认值,并期待它适合测试。
但是当我开始 运行 测试时,我发现数据库只重新创建了一次。由于测试没有按照预定义的顺序执行,因此这根本没有意义。
所以,问题是:如何理解?即 如何在每次测试之前重新创建数据库,就像在应用程序首次启动时发生的那样?
我的测试class header如下:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {
如您所见,我在 class 级别尝试了 @DirtiesContext
,但没有帮助。
更新
我有一颗豆子
@Service
public class DatabaseService implements InitializingBean {
其中有一个方法
@Override
@Transactional()
public void afterPropertiesSet() throws Exception {
log.info("Bootstrapping data...");
User user = createRootUser();
if(populateDemo) {
populateDemos();
}
log.info("...Bootstrapping completed");
}
现在我使用 populateDemos()
方法来清除数据库中的所有数据。不幸的是,尽管@DirtiesContext
,它并没有在每次测试之前调用。为什么?
除非您使用某种 Spring-Data 集成(我根本不知道),否则这似乎是您需要自己实现的自定义逻辑。 Spring 不了解您的数据库、它的架构和表格。
假设使用 JUnit,编写适当的 @Before
和 @After
方法来设置和清理您的数据库、它的表和数据。您的测试可以自己编写它们需要的数据,并可能在适当的时候自行清理。
如果你用spring.jpa.hibernate.ddl-auto=create-drop
应该够用create/drop数据库了吧?
要创建数据库,您必须按照 spring.jpa.hibernate.ddl-auto=create-drop
中其他答案所说的进行操作,现在如果您打算在每次测试时更新数据库,那么 spring 提供了一个非常有用的注释
@Transactional(value=JpaConfiguration.TRANSACTION_MANAGER_NAME)
@Sql(executionPhase=ExecutionPhase.BEFORE_TEST_METHOD,scripts="classpath:/test-sql/group2.sql")
public class GroupServiceTest extends TimeoffApplicationTests {
来自此包 org.springframework.test.context.jdbc.Sql;
,您可以 运行 一个之前的测试方法和一个之后的测试方法。填充数据库。
关于每次创建数据库,假设您只希望您的测试具有 create-drop 选项,您可以使用带有此注释的自定义属性配置您的测试
@TestPropertySource(locations="classpath:application-test.properties")
public class TimeoffApplicationTests extends AbstractTransactionalJUnit4SpringContextTests{
希望对您有所帮助
实际上,我想你想要这个:
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
@DirtiesContext may be used as a class-level and method-level
annotation within the same class. In such scenarios, the
ApplicationContext will be marked as dirty after any such annotated
method as well as after the entire class. If the
DirtiesContext.ClassMode is set to AFTER_EACH_TEST_METHOD, the context
will be marked dirty after each test method in the class.
你把它放在你的测试中 class。
通过spring 启动,可以为每个测试唯一地定义h2 数据库。只需覆盖每个测试的数据源 URL
@SpringBootTest(properties = {"spring.config.name=myapp-test-h2","myapp.trx.datasource.url=jdbc:h2:mem:trxServiceStatus"})
测试可以 运行 并行。
在测试中可以通过
重置数据
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
如果您正在寻找 @DirtiesContext
的替代方案,下面的代码将对您有所帮助。我使用了 this answer.
中的一些代码
首先,在测试资源文件夹的 application.yml
文件中设置 H2 数据库:
spring:
datasource:
platform: h2
url: jdbc:h2:mem:test
driver-class-name: org.h2.Driver
username: sa
password:
之后,创建一个名为 ResetDatabaseTestExecutionListener
的 class:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;
public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {
@Autowired
private DataSource dataSource;
public final int getOrder() {
return 2001;
}
private boolean alreadyCleared = false;
@Override
public void beforeTestClass(TestContext testContext) {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
}
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!alreadyCleared) {
cleanupDatabase();
alreadyCleared = true;
}
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
cleanupDatabase();
}
private void cleanupDatabase() throws SQLException {
Connection c = dataSource.getConnection();
Statement s = c.createStatement();
// Disable FK
s.execute("SET REFERENTIAL_INTEGRITY FALSE");
// Find all tables and truncate them
Set<String> tables = new HashSet<>();
ResultSet rs = s.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='PUBLIC'");
while (rs.next()) {
tables.add(rs.getString(1));
}
rs.close();
for (String table : tables) {
s.executeUpdate("TRUNCATE TABLE " + table);
}
// Idem for sequences
Set<String> sequences = new HashSet<>();
rs = s.executeQuery("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='PUBLIC'");
while (rs.next()) {
sequences.add(rs.getString(1));
}
rs.close();
for (String seq : sequences) {
s.executeUpdate("ALTER SEQUENCE " + seq + " RESTART WITH 1");
}
// Enable FK
s.execute("SET REFERENTIAL_INTEGRITY TRUE");
s.close();
c.close();
}
}
上面的代码将重置数据库(截断表、重置序列等)并准备使用 H2 数据库。如果您正在使用另一个内存数据库(如 HsqlDB),您需要对 SQL 查询进行必要的更改以完成相同的事情。
之后,去你的测试 class 并添加 @TestExecutionListeners
注释,如:
@TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {ResetDatabaseTestExecutionListener.class}
)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CreateOrderIT {
这应该有效。
如果您没有发现此方法与 @DirtiesContext
之间的任何性能差异,probably 您在测试中使用 @MockBean
,什么将 Spring 上下文标记为脏并自动重新加载整个上下文。
您可以使用 @Transactional
:
注释您的测试 class
import org.springframework.transaction.annotation.Transactional;
...
...
@RunWith(SpringRunner.class)
@Transactional
public class MyClassTest {
@Autowired
private SomeRepository repository;
@Before
public void init() {
// add some test data, that data would be rolled back, and recreated for each separate test
repository.save(...);
}
@Test
public void testSomething() {
// add some more data
repository.save(...);
// update some base data
repository.delete(...);
// all the changes on database done in that test would be rolled back after test finish
}
}
所有测试都包含在一个事务中,在每个测试结束时回滚。不幸的是,该注释当然存在一些问题,您需要特别注意,例如当您的生产代码使用不同分数的事务时。
使用 Spring-Boot 2.2.0 中接受的答案,我看到 JDBC 与约束相关的语法错误:
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Constraint "FKEFFD698EA2E75FXEERWBO8IUT" already exists; SQL statement:
alter table foo add constraint FKeffd698ea2e75fxeerwbo8iut foreign key (bar) references bar [90045-200]
为了解决这个问题,我将 @AutoConfigureTestDatabase
添加到我的单元测试中(spring-boot-test-autoconfigure 的一部分):
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
@AutoConfigureTestDatabase(replace = Replace.ANY)
public class FooRepositoryTest { ... }
您也可以尝试 https://www.testcontainers.org/,它可以帮助您 运行 容器内的数据库,您也可以为每个测试创建一个新的数据库 运行。但是它会很慢,因为每次都必须创建一个容器并且必须启动数据库服务器,配置然后迁移必须运行,然后才能执行测试。
使用 try/resources 的解决方案和基于 的可配置架构。我们的问题是我们的 H2 数据库在测试用例之间泄露了数据。所以这个 Listener
在每个测试方法之前触发。
Listener
:
public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {
private static final List<String> IGNORED_TABLES = List.of(
"TABLE_A",
"TABLE_B"
);
private static final String SQL_DISABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY FALSE";
private static final String SQL_ENABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY TRUE";
private static final String SQL_FIND_TABLE_NAMES = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='%s'";
private static final String SQL_TRUNCATE_TABLE = "TRUNCATE TABLE %s.%s";
private static final String SQL_FIND_SEQUENCE_NAMES = "SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='%s'";
private static final String SQL_RESTART_SEQUENCE = "ALTER SEQUENCE %s.%s RESTART WITH 1";
@Autowired
private DataSource dataSource;
@Value("${schema.property}")
private String schema;
@Override
public void beforeTestClass(TestContext testContext) {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
}
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
cleanupDatabase();
}
private void cleanupDatabase() throws SQLException {
try (
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()
) {
statement.execute(SQL_DISABLE_REFERENTIAL_INTEGRITY);
Set<String> tables = new HashSet<>();
try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_TABLE_NAMES, schema))) {
while (resultSet.next()) {
tables.add(resultSet.getString(1));
}
}
for (String table : tables) {
if (!IGNORED_TABLES.contains(table)) {
statement.executeUpdate(String.format(SQL_TRUNCATE_TABLE, schema, table));
}
}
Set<String> sequences = new HashSet<>();
try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_SEQUENCE_NAMES, schema))) {
while (resultSet.next()) {
sequences.add(resultSet.getString(1));
}
}
for (String sequence : sequences) {
statement.executeUpdate(String.format(SQL_RESTART_SEQUENCE, schema, sequence));
}
statement.execute(SQL_ENABLE_REFERENTIAL_INTEGRITY);
}
}
}
使用自定义注释:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = { ResetDatabaseTestExecutionListener.class }
)
public @interface ResetDatabase {
}
您可以轻松标记要重置数据库的每个测试:
@SpringBootTest(
webEnvironment = RANDOM_PORT,
classes = { Application.class }
)
@ResetDatabase
public class SomeClassIT {
对我没有任何作用,但以下内容:
对于每个测试 class 您可以添加以下注释:
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) //in case you need tests to be in specific order
@DataJpaTest // will disable full auto-configuration and instead apply only configuration relevant to JPA tests
@AutoConfigureTestDatabase(replace = NONE) //configures a test database to use instead of the application-defined or auto-configured DataSource
要在 class 中订购特定测试,您还必须添加 @Order 注释:
@Test
@Order(1) //first test
@Test
@Order(2) //second test, etc.
重新运行测试不会因为之前对 db 的操作而失败。
JUnit 5 测试中有涵盖“重置 H2 数据库”功能的库:
https://github.com/cronn/test-utils#h2util
示例用法:
@ExtendWith(SpringExtension.class)
@Import(H2Util.class)
class MyTest {
@BeforeEach
void resetDatabase(@Autowired H2Util h2Util) {
h2Util.resetDatabase();
}
// tests...
}
Maven 坐标:
<dependency>
<groupId>de.cronn</groupId>
<artifactId>test-utils</artifactId>
<version>0.2.0</version>
<scope>test</scope>
</dependency>
免责声明:我是建议库的作者。
我的 Spring-Boot-Mvc-Web 应用程序在 application.properties
文件中具有以下数据库配置:
spring.datasource.url=jdbc:h2:tcp://localhost/~/pdk
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
这是我做的唯一配置。我在任何地方都没有进行任何其他配置。尽管如此,Spring 和子系统会在每个 Web 应用程序 运行 上自动重新创建数据库。数据库在系统 运行 上重新创建,而它包含应用程序结束后的数据。
我不理解这个默认值,并期待它适合测试。
但是当我开始 运行 测试时,我发现数据库只重新创建了一次。由于测试没有按照预定义的顺序执行,因此这根本没有意义。
所以,问题是:如何理解?即 如何在每次测试之前重新创建数据库,就像在应用程序首次启动时发生的那样?
我的测试class header如下:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {
如您所见,我在 class 级别尝试了 @DirtiesContext
,但没有帮助。
更新
我有一颗豆子
@Service
public class DatabaseService implements InitializingBean {
其中有一个方法
@Override
@Transactional()
public void afterPropertiesSet() throws Exception {
log.info("Bootstrapping data...");
User user = createRootUser();
if(populateDemo) {
populateDemos();
}
log.info("...Bootstrapping completed");
}
现在我使用 populateDemos()
方法来清除数据库中的所有数据。不幸的是,尽管@DirtiesContext
,它并没有在每次测试之前调用。为什么?
除非您使用某种 Spring-Data 集成(我根本不知道),否则这似乎是您需要自己实现的自定义逻辑。 Spring 不了解您的数据库、它的架构和表格。
假设使用 JUnit,编写适当的 @Before
和 @After
方法来设置和清理您的数据库、它的表和数据。您的测试可以自己编写它们需要的数据,并可能在适当的时候自行清理。
如果你用spring.jpa.hibernate.ddl-auto=create-drop
应该够用create/drop数据库了吧?
要创建数据库,您必须按照 spring.jpa.hibernate.ddl-auto=create-drop
中其他答案所说的进行操作,现在如果您打算在每次测试时更新数据库,那么 spring 提供了一个非常有用的注释
@Transactional(value=JpaConfiguration.TRANSACTION_MANAGER_NAME)
@Sql(executionPhase=ExecutionPhase.BEFORE_TEST_METHOD,scripts="classpath:/test-sql/group2.sql")
public class GroupServiceTest extends TimeoffApplicationTests {
来自此包 org.springframework.test.context.jdbc.Sql;
,您可以 运行 一个之前的测试方法和一个之后的测试方法。填充数据库。
关于每次创建数据库,假设您只希望您的测试具有 create-drop 选项,您可以使用带有此注释的自定义属性配置您的测试
@TestPropertySource(locations="classpath:application-test.properties")
public class TimeoffApplicationTests extends AbstractTransactionalJUnit4SpringContextTests{
希望对您有所帮助
实际上,我想你想要这个:
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
@DirtiesContext may be used as a class-level and method-level annotation within the same class. In such scenarios, the ApplicationContext will be marked as dirty after any such annotated method as well as after the entire class. If the DirtiesContext.ClassMode is set to AFTER_EACH_TEST_METHOD, the context will be marked dirty after each test method in the class.
你把它放在你的测试中 class。
通过spring 启动,可以为每个测试唯一地定义h2 数据库。只需覆盖每个测试的数据源 URL
@SpringBootTest(properties = {"spring.config.name=myapp-test-h2","myapp.trx.datasource.url=jdbc:h2:mem:trxServiceStatus"})
测试可以 运行 并行。
在测试中可以通过
重置数据@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
如果您正在寻找 @DirtiesContext
的替代方案,下面的代码将对您有所帮助。我使用了 this answer.
首先,在测试资源文件夹的 application.yml
文件中设置 H2 数据库:
spring:
datasource:
platform: h2
url: jdbc:h2:mem:test
driver-class-name: org.h2.Driver
username: sa
password:
之后,创建一个名为 ResetDatabaseTestExecutionListener
的 class:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;
public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {
@Autowired
private DataSource dataSource;
public final int getOrder() {
return 2001;
}
private boolean alreadyCleared = false;
@Override
public void beforeTestClass(TestContext testContext) {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
}
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!alreadyCleared) {
cleanupDatabase();
alreadyCleared = true;
}
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
cleanupDatabase();
}
private void cleanupDatabase() throws SQLException {
Connection c = dataSource.getConnection();
Statement s = c.createStatement();
// Disable FK
s.execute("SET REFERENTIAL_INTEGRITY FALSE");
// Find all tables and truncate them
Set<String> tables = new HashSet<>();
ResultSet rs = s.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='PUBLIC'");
while (rs.next()) {
tables.add(rs.getString(1));
}
rs.close();
for (String table : tables) {
s.executeUpdate("TRUNCATE TABLE " + table);
}
// Idem for sequences
Set<String> sequences = new HashSet<>();
rs = s.executeQuery("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='PUBLIC'");
while (rs.next()) {
sequences.add(rs.getString(1));
}
rs.close();
for (String seq : sequences) {
s.executeUpdate("ALTER SEQUENCE " + seq + " RESTART WITH 1");
}
// Enable FK
s.execute("SET REFERENTIAL_INTEGRITY TRUE");
s.close();
c.close();
}
}
上面的代码将重置数据库(截断表、重置序列等)并准备使用 H2 数据库。如果您正在使用另一个内存数据库(如 HsqlDB),您需要对 SQL 查询进行必要的更改以完成相同的事情。
之后,去你的测试 class 并添加 @TestExecutionListeners
注释,如:
@TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {ResetDatabaseTestExecutionListener.class}
)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CreateOrderIT {
这应该有效。
如果您没有发现此方法与 @DirtiesContext
之间的任何性能差异,probably 您在测试中使用 @MockBean
,什么将 Spring 上下文标记为脏并自动重新加载整个上下文。
您可以使用 @Transactional
:
import org.springframework.transaction.annotation.Transactional;
...
...
@RunWith(SpringRunner.class)
@Transactional
public class MyClassTest {
@Autowired
private SomeRepository repository;
@Before
public void init() {
// add some test data, that data would be rolled back, and recreated for each separate test
repository.save(...);
}
@Test
public void testSomething() {
// add some more data
repository.save(...);
// update some base data
repository.delete(...);
// all the changes on database done in that test would be rolled back after test finish
}
}
所有测试都包含在一个事务中,在每个测试结束时回滚。不幸的是,该注释当然存在一些问题,您需要特别注意,例如当您的生产代码使用不同分数的事务时。
使用 Spring-Boot 2.2.0 中接受的答案,我看到 JDBC 与约束相关的语法错误:
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Constraint "FKEFFD698EA2E75FXEERWBO8IUT" already exists; SQL statement: alter table foo add constraint FKeffd698ea2e75fxeerwbo8iut foreign key (bar) references bar [90045-200]
为了解决这个问题,我将 @AutoConfigureTestDatabase
添加到我的单元测试中(spring-boot-test-autoconfigure 的一部分):
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
@AutoConfigureTestDatabase(replace = Replace.ANY)
public class FooRepositoryTest { ... }
您也可以尝试 https://www.testcontainers.org/,它可以帮助您 运行 容器内的数据库,您也可以为每个测试创建一个新的数据库 运行。但是它会很慢,因为每次都必须创建一个容器并且必须启动数据库服务器,配置然后迁移必须运行,然后才能执行测试。
使用 try/resources 的解决方案和基于 Listener
在每个测试方法之前触发。
Listener
:
public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {
private static final List<String> IGNORED_TABLES = List.of(
"TABLE_A",
"TABLE_B"
);
private static final String SQL_DISABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY FALSE";
private static final String SQL_ENABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY TRUE";
private static final String SQL_FIND_TABLE_NAMES = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='%s'";
private static final String SQL_TRUNCATE_TABLE = "TRUNCATE TABLE %s.%s";
private static final String SQL_FIND_SEQUENCE_NAMES = "SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='%s'";
private static final String SQL_RESTART_SEQUENCE = "ALTER SEQUENCE %s.%s RESTART WITH 1";
@Autowired
private DataSource dataSource;
@Value("${schema.property}")
private String schema;
@Override
public void beforeTestClass(TestContext testContext) {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
}
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
cleanupDatabase();
}
private void cleanupDatabase() throws SQLException {
try (
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()
) {
statement.execute(SQL_DISABLE_REFERENTIAL_INTEGRITY);
Set<String> tables = new HashSet<>();
try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_TABLE_NAMES, schema))) {
while (resultSet.next()) {
tables.add(resultSet.getString(1));
}
}
for (String table : tables) {
if (!IGNORED_TABLES.contains(table)) {
statement.executeUpdate(String.format(SQL_TRUNCATE_TABLE, schema, table));
}
}
Set<String> sequences = new HashSet<>();
try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_SEQUENCE_NAMES, schema))) {
while (resultSet.next()) {
sequences.add(resultSet.getString(1));
}
}
for (String sequence : sequences) {
statement.executeUpdate(String.format(SQL_RESTART_SEQUENCE, schema, sequence));
}
statement.execute(SQL_ENABLE_REFERENTIAL_INTEGRITY);
}
}
}
使用自定义注释:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = { ResetDatabaseTestExecutionListener.class }
)
public @interface ResetDatabase {
}
您可以轻松标记要重置数据库的每个测试:
@SpringBootTest(
webEnvironment = RANDOM_PORT,
classes = { Application.class }
)
@ResetDatabase
public class SomeClassIT {
对我没有任何作用,但以下内容: 对于每个测试 class 您可以添加以下注释:
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) //in case you need tests to be in specific order
@DataJpaTest // will disable full auto-configuration and instead apply only configuration relevant to JPA tests
@AutoConfigureTestDatabase(replace = NONE) //configures a test database to use instead of the application-defined or auto-configured DataSource
要在 class 中订购特定测试,您还必须添加 @Order 注释:
@Test
@Order(1) //first test
@Test
@Order(2) //second test, etc.
重新运行测试不会因为之前对 db 的操作而失败。
JUnit 5 测试中有涵盖“重置 H2 数据库”功能的库:
https://github.com/cronn/test-utils#h2util
示例用法:
@ExtendWith(SpringExtension.class)
@Import(H2Util.class)
class MyTest {
@BeforeEach
void resetDatabase(@Autowired H2Util h2Util) {
h2Util.resetDatabase();
}
// tests...
}
Maven 坐标:
<dependency>
<groupId>de.cronn</groupId>
<artifactId>test-utils</artifactId>
<version>0.2.0</version>
<scope>test</scope>
</dependency>
免责声明:我是建议库的作者。