如何在Spring中的每次测试之前重新创建数据库?

Dim*_*ims 42 java spring spring-mvc spring-test spring-boot

我的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
Run Code Online (Sandbox Code Playgroud)

这是我做的唯一配置.我没有任何其他配置.尽管如此,Spring和子系统会在每次运行Web应用程序时自动重新创建数据库.数据库在系统运行时重新创建,而在应用程序结束后包含数据.

我不理解这个默认值,并期望这适合测试.

但是当我开始运行测试时,我发现数据库只重建了一次.由于测试是在没有预定义的顺序执行的,所以这是毫无意义的.

所以,问题是:如何理解?即如何在应用程序首次启动时在每次测试之前重新创建数据库?

我的测试类标题如下:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {
Run Code Online (Sandbox Code Playgroud)

如你所见,我@DirtiesContext在课堂上尝试过并没有帮助.

UPDATE

我有一个豆子

@Service
public class DatabaseService implements InitializingBean {
Run Code Online (Sandbox Code Playgroud)

哪个有方法

@Override
    @Transactional()
    public void afterPropertiesSet() throws Exception {
        log.info("Bootstrapping data...");
        User user = createRootUser();
        if(populateDemo) {
            populateDemos();
        }
        log.info("...Bootstrapping completed");
    }
Run Code Online (Sandbox Code Playgroud)

现在我做populateDemos()了清除数据库中所有数据的方法.不幸的是,它在每次测试之前都没有调用过@DirtiesContext.为什么?

Rap*_*edo 70

实际上,我想你想要这个:

@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)

http://docs.spring.io/autorepo/docs/spring-framework/4.2.6.RELEASE/javadoc-api/org/springframework/test/annotation/DirtiesContext.html

@DirtiesContext可以用作同一类中的类级别和方法级别注释.在这种情况下,ApplicationContext将在任何此类带注释的方法之后以及在整个类之后标记为脏.如果将DirtiesContext.ClassMode设置为AFTER_EACH_TEST_METHOD,则在类中的每个测试方法之后,上下文将被标记为脏.

  • 我认为肮脏的环境对性能有很大的影响。通常,每次测试后仅回滚事务以重置数据库。 (8认同)
  • 这个解决方案在某种程度上对我有用,因为它在某些环境中有效,但在其他环境中无效。为了避免不稳定,我将 spring 配置为每次重新加载配置时使用不同的测试数据库: `spring.datasource.url=jdbc:h2:mem:${random.uuid}` (6认同)
  • 由于某种原因,它没有清除内存数据库中的“h2”。 (5认同)
  • 它不会产生重复的键冲突,因为它会重新创建数据库,而不仅仅是删除表中的所有值。它将删除数据库。因此,每个测试都将在一个全新的数据库上运行。这样,测试不会影响另一个测试。 (2认同)
  • @lapots我只是在调查类似的问题并找到了解决方案,但是不能说为什么它对我确实有效。我的设置是:Spring-boot5,junit5,内存H2,类级别的DirtiesContext。我发现的是,当H2 url被命名为'jdbc:h2:mem:mem1'(此处的mem1很重要)时,则测试失败(mvn测试)。但是使H2 url像'jdbc:h2:mem'一样可以解决它! (2认同)

Zac*_*ack 51

使用 Spring-Boot 2.2.0 中接受的答案,我看到了与约束相关的 JDBC 语法错误:

引起:org.h2.jdbc.JdbcSQLSyntaxErrorException:约束“FKEFFD698EA2E75FXEERWBO8IUT”已经存在;SQL 语句:alter table foo 添加约束 FKeffd698ea2e75fxeerwbo8iut 外键 (bar) 引用 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 { ... }
Run Code Online (Sandbox Code Playgroud)

  • 升级到 Spring-Boot 2.2.x 后,我有了这个表面。我只希望我能不止一次地对此投赞成票。花了半天时间想办法解决这个问题。 (8认同)

jst*_*lne 12

要创建数据库,您必须执行其他答案所说的内容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 {
Run Code Online (Sandbox Code Playgroud)

来自此包org.springframework.test.context.jdbc.Sql;,您可以运行before测试方法和after测试方法.填充数据库.

关于每次创建数据库,假设您只希望测试具有create-drop选项,您可以使用带有此批注的自定义属性配置测试

@TestPropertySource(locations="classpath:application-test.properties")
public class TimeoffApplicationTests extends AbstractTransactionalJUnit4SpringContextTests{
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你


Dhe*_*rik 10

如果您正在寻找 的替代品@DirtiesContext,下面的代码将对您有所帮助。我使用了这个答案中的一些代码。

首先,application.yml在测试资源文件夹中的文件上设置 H2 数据库:

spring: 
  datasource:
    platform: h2
    url: jdbc:h2:mem:test
    driver-class-name: org.h2.Driver
    username: sa
    password:
Run Code Online (Sandbox Code Playgroud)

之后,创建一个名为的类ResetDatabaseTestExecutionListener

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码将重置数据库(截断表、重置序列等)并准备使用 H2 数据库。如果您使用另一个内存数据库(如 HsqlDB),您需要对 SQL 查询进行必要的更改以完成相同的操作。

之后,转到您的测试类并添加@TestExecutionListeners注释,例如:

@TestExecutionListeners(mergeMode =
        TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
        listeners = {ResetDatabaseTestExecutionListener.class}
)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CreateOrderIT {
Run Code Online (Sandbox Code Playgroud)

这应该有效。

如果您没有看到此方法与 之间的任何性能差异@DirtiesContext可能您正在@MockBean测试内部使用,将上下文标记为脏并自动重新加载 Spring 上下文。

  • 这对我们有用!我已对其进行了调整以供我们使用,您可以在此处查看代码:/sf/answers/4708372721/ (2认同)

Int*_*ted 9

使用spring boot,可以为每个测试唯一定义h2数据库。只需覆盖每个测试的数据源URL

 @SpringBootTest(properties = {"spring.config.name=myapp-test-h2","myapp.trx.datasource.url=jdbc:h2:mem:trxServiceStatus"})
Run Code Online (Sandbox Code Playgroud)

这些测试可以并行运行。

在测试中,可以通过以下方式重置数据:

@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
Run Code Online (Sandbox Code Playgroud)


Rad*_*icz 8

有一个库涵盖了 JUnit 5 测试中的“重置 H2 数据库”功能:

\n

https://github.com/cronn/test-utils#h2util

\n

使用示例:

\n
@ExtendWith(SpringExtension.class)\n@Import(H2Util.class)\nclass MyTest {\n\n    @BeforeEach\n    void resetDatabase(@Autowired H2Util h2Util) {\n        h2Util.resetDatabase();\n    }\n\n    // tests...\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Maven 坐标:

\n
<dependency>\n    <groupId>de.cronn</groupId>\n    <artifactId>test-utils</artifactId>\n    <version>0.2.0</version>\n    <scope>test</scope>\n</dependency>\n
Run Code Online (Sandbox Code Playgroud)\n

免责声明:我\xe2\x80\x99m 建议库的作者。

\n


小智 5

如果使用spring.jpa.hibernate.ddl-auto=create-drop就足以创建/删除数据库?