我的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应用程序时自动重新创建数据库。数据库在系统运行时重新创建,而在应用程序结束后包含数据。
我不理解这个默认值,并期望它适合测试。
但是当我开始运行测试时,我发现数据库只重建了一次。由于测试是在没有预定义的顺序执行的,所以这一点都没有意义。
所以,问题是:如何理解?即。如何在每次测试之前重新创建数据库,因为它在应用程序首次启动时会发生?
我的测试类标题如下:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {
如您所见,我在班级尝试了@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
,它在每次测试之前都没有调用。为什么呢?
答案 0 :(得分:61)
实际上,我认为你想要这个:
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
@DirtiesContext可以用作类级别和方法级别 同一类中的注释。在这种情况下, 在任何此类注释后,ApplicationContext将被标记为脏 方法以及整个班级之后。如果 DirtiesContext.ClassMode设置为AFTER_EACH_TEST_METHOD,即上下文 在课堂上的每个测试方法后都会被标记为脏。
答案 1 :(得分:8)
要创建数据库,您必须使用spring.jpa.hibernate.ddl-auto=create-drop
执行其他答案所说的内容,现在,如果您的目的是在每个测试中对数据库进行傀儡,那么spring提供了非常有用的anotation
@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;
,您可以运行before测试方法和after测试方法。填充数据库。
关于每次创建数据库,假设您只希望测试具有create-drop选项,您可以使用带有此批注的自定义属性配置测试
@TestPropertySource(locations="classpath:application-test.properties")
public class TimeoffApplicationTests extends AbstractTransactionalJUnit4SpringContextTests{
希望有所帮助
答案 2 :(得分:5)
使用spring boot,可以为每个测试唯一地定义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)
答案 3 :(得分:4)
如果使用spring.jpa.hibernate.ddl-auto=create-drop
应该足以创建/删除数据库?
答案 4 :(得分:3)
使用Spring-Boot 2.2.0中可接受的答案,我看到与约束有关的JDBC语法错误:
由以下原因引起:org.h2.jdbc.JdbcSQLSyntaxErrorException:约束“ FKEFFD698EA2E75FXEERWBO8IUT”已经存在; SQL语句: 更改表foo添加约束FKeffd698ea2e75fxeerwbo8iut外键(栏)引用栏[90045-200]
为解决此问题,我在单元测试中添加了@AutoConfiguredTestDatabase
:
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 { ... }
答案 5 :(得分:2)
除非你使用某种Spring-Data集成(我根本不知道),这似乎是你需要自己实现的自定义逻辑。 Spring不了解您的数据库,其模式和表。
假设使用JUnit,请编写适当的@Before
和@After
方法来设置和清理数据库,其表和数据。您的测试本身可以编写他们需要的数据,并在适当的情况下自行清理。
答案 6 :(得分:1)
如果您正在寻找class Driver
的替代方法,则下面的代码将为您提供帮助。我使用了this answer中的一些代码。
首先,在测试资源文件夹的Handler
文件上设置H2数据库:
@DirtiesContext
然后,创建一个名为application.yml
的类:
spring:
datasource:
platform: h2
url: jdbc:h2:mem:test
driver-class-name: org.h2.Driver
username: sa
password:
上面的代码将重置数据库(截断表,重置序列等),并且仅适用于H2。如果您正在使用其他内存数据库(例如HsqlDB),则需要对SQL进行必要的更改以完成相同的操作。
之后,转到测试类并添加ResetDatabaseTestExecutionListener
批注,例如:
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();
}
}
这应该有效。
老实说,使用这种方法或@TestExecutionListeners
不会看到任何性能差异,但是也许在更大的应用程序中,这可以提高集成测试性能。
答案 7 :(得分:1)
使用 try/resources 和基于 this answer 的可配置架构的解决方案。我们的问题是我们的 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 {
答案 8 :(得分:0)
您可以使用@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
}
}
所有测试都包装在一个事务中,该事务将在每个测试结束时回滚。不幸的是,该注释当然存在一些问题,例如当您的生产代码使用具有不同分数的事务时,您需要特别注意。
答案 9 :(得分:0)
对我没有任何作用,但以下内容: 对于每个测试类,您可以添加以下注释:
@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
要在类中订购特定的测试,您还必须添加@Order 注释:
@Test
@Order(1) //first test
@Test
@Order(2) //second test, etc.
重新运行测试不会因为之前对 db 的操作而失败。
答案 10 :(得分:-1)
您还可以尝试使用https://www.testcontainers.org/,它可以帮助您在容器内运行数据库,也可以为每次测试运行创建一个新的数据库。但是,这将非常慢,因为每次必须创建一个容器并且必须启动,配置数据库服务器,然后必须运行迁移,然后才能执行测试。