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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34617152/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 15:50:42  来源:igfitidea点击:

How to re-create database before each test in Spring?

javaspringspring-mvcspring-bootspring-test

提问by Dims

My Spring-Boot-Mvc-Web application has the following database configuration in application.propertiesfile:

我的 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

this is the only config I made. No any other configurations made by me anywhere. Nevertheless the Spring and subsystems are automatically recreate database on each web application run. Database is recreated namely on system run while it contains data after application ends.

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

I was not understanding this defaults and was expecting this is suitable for tests.

我不理解这个默认值,并希望这适用于测试。

But when I started to run tests I found that database is recreated only once. Since tests are executed at no predefined order, this is senseless at all.

但是当我开始运行测试时,我发现数据库只重新创建了一次。由于测试没有按预定义的顺序执行,这完全没有意义。

So, the question is: how to make any sense? I.e.how to make database recreate before each test as it happens at application first start?

所以,问题是:如何有意义?即如何在每次测试之前重新创建数据库,因为它发生在应用程序首次启动时?

My test class header is follows:

我的测试类标题如下:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {

As you see, I tried @DirtiesContextat class level and it didn't help.

如您所见,我@DirtiesContext在班级级别进行了尝试,但没有帮助。

UPDATE

更新

I have a bean

我有一颗豆

@Service
public class DatabaseService implements InitializingBean {

which has a method

它有一个方法

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

Now I made it's populateDemos()method to clear all data from database. Unfortunately, it does not called before each test despite @DirtiesContext. Why?

现在我做了它的populateDemos()方法来清除数据库中的所有数据。不幸的是,尽管@DirtiesContext. 为什么?

采纳答案by Raphael Amoedo

Actually, I think you want this:

其实,我想你想要这个:

@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)

@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

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

@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.

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

回答by Sotirios Delimanolis

Unless you're using some kind of Spring-Data integration (which I don't know at all), this seems like custom logic you'll need to implement yourself. Spring doesn't know about your databases, its schemas, and tables.

除非您使用某种 Spring-Data 集成(我根本不知道),否则这似乎是您需要自己实现的自定义逻辑。Spring 不知道您的数据库、其模式和表。

Assuming JUnit, write appropriate @Beforeand @Aftermethods to set up and clean up your database, its tables, and data. Your tests can themselves write the data they need, and potentially clean up after themselves if appropriate.

假设JUnit中,写入适当@Before@After方法来设置和清理你的数据库,它的表和数据。您的测试可以自己编写他们需要的数据,并在适当的情况下自己进行清理。

回答by user2669657

If you use spring.jpa.hibernate.ddl-auto=create-dropshould be enough to create/drop database?

如果你使用spring.jpa.hibernate.ddl-auto=create-drop创建/删除数据库应该够了吗?

回答by jstuartmilne

To create the database you have to do what the other answers say with the spring.jpa.hibernate.ddl-auto=create-drop, now if your intent is to pupulate the database on each test then spring provides a very usefull anotation

要创建数据库,您必须按照其他答案所说的使用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 {

that is from this package org.springframework.test.context.jdbc.Sql;and you can run a before test method and a after test method. To populate the database.

来自这个包org.springframework.test.context.jdbc.Sql;,你可以运行一个测试前的方法和一个测试后的方法。填充数据库。

Regarding creating the database each time, Say you only want your Test to have the create-drop option you can configure your tests with a custom properties with this annotation

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

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

Hope it helps

希望能帮助到你

回答by Interlated

With spring boot the h2 database can be defined uniquely for each test. Just override the data source URL for each test

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

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

The tests can run in parallel.

测试可以并行运行。

Within the test the data can be reset by

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

@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)

回答by Dherik

If you are looking for an alternative for the @DirtiesContext, this code below will help you. I used some code from this answer.

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

First, setup the H2 database on the application.ymlfile on your test resources folder:

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

spring: 
  datasource:
    platform: h2
    url: jdbc:h2:mem:test
    driver-class-name: org.h2.Driver
    username: sa
    password:

After that, create a class called ResetDatabaseTestExecutionListener:

之后,创建一个名为的类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();
    }
}

The code above will reset the database (truncate tables, reset sequences, etc) and is prepared to work with H2 database. If you are using another memory database (like HsqlDB) you need to make the necessary changes on the SQLs queries to accomplish the same thing.

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

After that, go to your test class and add the @TestExecutionListenersannotation, like:

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

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

This should work.

这应该有效。

If you not see any performance difference between this approach and @DirtiesContext, probablyyou are using @MockBeaninside of your tests, what mark the context as dirty and automatically reload the Spring context.

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

回答by bladekp

You can annotate your test class with @Transactional:

您可以使用以下命令注释您的测试类@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
    }
}

All tests are wrapped inside a transaction, that is rolled back at the end of each test. There are unfortunately some problems with that annotation of course, and you need to pay special attention, when for example your production code uses transactions with different score.

所有测试都包含在一个事务中,在每个测试结束时回滚。不幸的是,该注释当然存在一些问题,您需要特别注意,例如当您的生产代码使用不同分数的事务时。

回答by Zack

Using the accepted answer in Spring-Boot 2.2.0, I was seeing JDBC syntax errors related to constraints:

使用 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]

引起:org.h2.jdbc.JdbcSQLSyntaxErrorException:约束“FKEFFD698EA2E75FXEERWBO8IUT”已经存在;SQL 语句:alter table foo 添加约束 FKeffd698ea2e75fxeerwbo8iut 外键 (bar) 引用 bar [90045-200]

To fix this, I added @AutoConfiguredTestDatabaseto my unit test:

为了解决这个问题,我添加@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 { ... }