web-dev-qa-db-ja.com

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アプリケーションの実行で自動的にデータベースを再作成します。データベースは、アプリケーションの終了後にデータが含まれている間、つまりシステム実行時に再作成されます。

私はこのデフォルトを理解しておらず、これがテストに適していると期待していました。

しかし、テストを実行し始めたとき、データベースが1回だけ再作成されることがわかりました。テストは事前定義された順序で実行されないため、これはまったく無意味です。

したがって、質問は次のとおりです。 意味を成す方法は?つまり. アプリケーションの最初の起動時に行われる各テストの前にデータベースを再作成する方法は?

私のテストクラスヘッダーは次のとおりです。

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

ご覧のとおり、クラスレベルで@DirtiesContextを試しましたが、役に立ちませんでした。

UPDATE

私は豆を持っています

@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にもかかわらず、各テストの前には呼び出されません。どうして?

37
Dims

実際、これが欲しいと思う:

@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に設定されている場合、コンテキストはクラス内の各テストメソッドの後にダーティとしてマークされます。

65
Raphael Amoedo

データベースを作成するには、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{

それが役に立てば幸い

9
jstuartmilne

スプリングブートでは、テストごとに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)
5
Interlated

spring.jpa.hibernate.ddl-auto=create-dropを使用する場合、データベースを作成/削除するには十分ですか?

5
user2669657

何らかの種類のSpring-Data統合を使用していない限り(これはまったくわかりません)、これは自分で実装する必要があるカスタムロジックのようです。 Springは、データベース、そのスキーマ、およびテーブルについては知りません。

JUnitを想定して、適切な@Beforeおよび@Afterメソッドを記述して、データベース、そのテーブル、およびデータをセットアップおよびクリーンアップします。テスト自体が必要なデータを書き込むことができ、必要に応じてテスト後に潜在的にクリーンアップできます。

@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というクラスを作成します。

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クエリで必要な変更を行って同じことを達成する必要があります。

その後、テストクラスに移動して、次のような@TestExecutionListeners注釈を追加します。

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

これは動作するはずです。

このアプローチと@DirtiesContextのパフォーマンスに違いがない場合、 おそらく テスト内で@MockBeanを使用しているため、コンテキストをダーティとしてマークし、Springコンテキストを自動的にリロードします。 。

2
Dherik