web-dev-qa-db-ja.com

Hibernate ConstraintViolationException(またはSpring DataIgityViolationException?)がJUnit 5でSpring Bootでキャッチする方法

私はこの「Heisenbergの不確実性の責任はJava例外に伴う "であると考えましたが、それは(a)でした(a)、そして(b)十分に説明的ではありませんでした。

Bluf:私は、Spring Bootアプリケーションに対するJUnit 5テストで、タプルが制約違反を持つデータベーステーブルに永続化された場合にスローされた例外がキャッチしようとしています(「ユニーク」という列の重複値)。私はtry-catchブロックで例外をキャッチすることができますが、junitの "assertthrows()"を使用していません。

詳投

複製を簡単にするために、コードを絞り込み、企業とリポジトリのみに絞り込み、2つのテスト(もう1つはこの記事の理由です)。複製の容易さのためにも、データベースとしてH2を使用しています。

私は、呼び出しメソッドの範囲内で制約生成された例外がスローされないようにする可能性がある潜在的なトランザクションスコープの問題があることを読みました。私はこれを声明の周りの単純な試着ブロックで確認しました "foos.ave(foo);" shouldthRowExceptionSave()( "tem.flush()"ステートメントなしで)。

TestentityManager.Flush()を使用してトランザクションを強制的にコミット/終了させることを決定し、Try-Catchブロックで例外を正常にキャッチすることができました。ただし、予想されるDataIntegrityViolationExceptionではありませんでしたが、PersistenceExceptionです。

私は同様のメカニズムを使用しようとしました(すなわち、assertthylows()ステートメントに問題を強制するためにTestentyTityManager.Flush()を使用しようとしました。しかし、「喜びなし」。

「assertthrows(persistenceException.class、...」を試していると、このメソッドはDataIntegRityViolationExceptionで終了します。

「asserthrows(dataIntegrityViolationException.class、...」を試してください。

あらゆる助け/洞察が大いに感謝されるでしょう。

注意:ShephRowExceptionSave()のtry-catchブロックは、例外が捕捉されているのかを確認してください。

エンティティクラス

_package com.test.foo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Foo {

    @Id
    @Column(name     = "id",
            nullable = false,
            unique   = true)
    private String id;
    @Column(name     = "name",
            nullable = false,
            unique   = true)
    private String name;

    public Foo() {
        id   = "Default ID";
        name = "Default Name";
    }

    public Foo(String id, String name) {
        this.id   = id;
        this.name = name;
    }

    public String getId() { return id;}

    public void setName(String name) { this.name = name; }

    public String getName() { return name; }
}
_

リポジトリインタフェース

_package com.test.foo;

import org.springframework.data.repository.CrudRepository;

public interface FooRepository extends CrudRepository<Foo, String> { }
_

リポジトリテストクラス

_package com.test.foo;

import org.hibernate.exception.ConstraintViolationException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.dao.DataIntegrityViolationException;

import javax.persistence.PersistenceException;
import Java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

@DataJpaTest
public class FooRepositoryITest {

    @Autowired
    private TestEntityManager tem;

    @Autowired
    private FooRepository foos;

    private static final int    NUM_ROWS  = 25;
    private static final String BASE_ID   = "->Test Id";
    private static final String BASE_NAME = "->Test Name";

    @BeforeEach
    public void insertFooTuples() {
        Foo foo;

        for (int i=0; i<NUM_ROWS; i++) {
            foo = new Foo(i+BASE_ID, i+BASE_NAME);
            tem.persist(foo);
        }
        tem.flush();
    }

    @AfterEach
    public void removeFooTuples() {
        foos.findAll()
                .forEach(tem::remove);
        tem.flush();
    }

    @Test
    public void shouldSaveNewTyple() {
        Optional<Foo> newFoo;
        String        newId   = "New Test Id";
        String        newName = "New Test Name";
        Foo           foo     = new Foo(newId, newName);

        foos.save(foo);
        tem.flush();

        newFoo = foos.findById(newId);
        assertTrue(newFoo.isPresent(), "Failed to add Foo Tuple");
    }

    @Test
    public void shouldThrowExceptionOnSave() {
        Optional<Foo> newFoo;
        String        newId   = "New Test Id";
        String        newName = "New Test Name";
        Foo           foo     = new Foo(newId, newName);

        foo.setName(foos.findById(1+BASE_ID).get().getName());

        try {
            foos.save(foo);
            tem.flush();
        } catch(PersistenceException e) {
            System.out.println("\n\n**** IN CATCH BLOCK ****\n\n");
            System.out.println(e.toString());
        }

//        assertThrows(DataIntegrityViolationException.class,
//        assertThrows(ConstraintViolationException.class,
        assertThrows(PersistenceException.class,
                () -> { foos.save(foo);
                        tem.flush();
                      } );
    }
}
_

build.gradle.

_plugins {
    id 'org.springframework.boot' version '2.1.3.RELEASE'
    id 'Java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.test'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
    implementation('org.springframework.boot:spring-boot-starter-web')
    runtimeOnly('com.h2database:h2')
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'junit'
        exclude group: 'org.hamcrest'
    }
    testImplementation('org.junit.jupiter:junit-jupiter:5.4.0')
    testImplementation('com.h2database:h2')
}

test {
    useJUnitPlatform()
}
_

「アサートゥーロ(PersitenteCexception、...)で出力します。

_2019-02-25 14:55:12.747  WARN 15796 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23505, SQLState: 23505
2019-02-25 14:55:12.747 ERROR 15796 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Unique index or primary key violation: "UK_A7S9IMMDPCXHLN2D4JHLAY516_INDEX_1 ON PUBLIC.FOO(NAME) VALUES ('1->Test Name', 2)"; SQL statement:
insert into foo (name, id) values (?, ?) [23505-197]

**** IN CATCH BLOCK ****

javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
.
. (some debug output removed for brevity)
.
2019-02-25 14:55:12.869  WARN 15796 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23505, SQLState: 23505
2019-02-25 14:55:12.869 ERROR 15796 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Unique index or primary key violation: "UK_A7S9IMMDPCXHLN2D4JHLAY516_INDEX_1 ON PUBLIC.FOO(NAME) VALUES ('1->Test Name', 2)"; SQL statement:
insert into foo (name, id) values (?, ?) [23505-197]
2019-02-25 14:55:12.877  INFO 15796 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@313ac989 testClass = FooRepositoryITest, testInstance = com.test.foo.FooRepositoryITest@71d44a3, testMethod = shouldThrowExceptionOnSave@FooRepositoryITest, testException = org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["UK_A7S9IMMDPCXHLN2D4JHLAY516_INDEX_1 ON PUBLIC.FOO(NAME) VALUES ('1->Test Name', 2)"; SQL statement:
insert into foo (name, id) values (?, ?) [23505-197]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement, mergedContextConfiguration = [MergedContextConfiguration@4562e04d testClass = FooRepositoryITest, locations = '{}', classes = '{class com.test.foo.FooApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@527e5409, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@8b41920b, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2a32de6c, [ImportsContextCustomizer@2a65fe7c key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@147ed70f, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@15b204a1, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["UK_A7S9IMMDPCXHLN2D4JHLAY516_INDEX_1 ON PUBLIC.FOO(NAME) VALUES ('1->Test Name', 2)"; SQL statement:
insert into foo (name, id) values (?, ?) [23505-197]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
_

AssertthRows(DataIntegrityViolationException、...)を使用して出力

_2019-02-25 14:52:16.880  WARN 2172 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23505, SQLState: 23505
2019-02-25 14:52:16.880 ERROR 2172 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Unique index or primary key violation: "UK_A7S9IMMDPCXHLN2D4JHLAY516_INDEX_1 ON PUBLIC.FOO(NAME) VALUES ('1->Test Name', 2)"; SQL statement:
insert into foo (name, id) values (?, ?) [23505-197]

**** IN CATCH BLOCK ****

javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
.
. (some debug output removed for brevity)
.
insert into foo (name, id) values (?, ?) [23505-197]
2019-02-25 14:52:16.974  INFO 2172 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@313ac989 testClass = FooRepositoryITest, testInstance = com.test.foo.FooRepositoryITest@71d44a3, testMethod = shouldThrowExceptionOnSave@FooRepositoryITest, testException = org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <org.springframework.dao.DataIntegrityViolationException> but was: <javax.persistence.PersistenceException>, mergedContextConfiguration = [MergedContextConfiguration@4562e04d testClass = FooRepositoryITest, locations = '{}', classes = '{class com.test.foo.FooApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@527e5409, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@8b41920b, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2a32de6c, [ImportsContextCustomizer@2a65fe7c key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@147ed70f, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@15b204a1, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]

org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> 
Expected :<org.springframework.dao.DataIntegrityViolationException> 
Actual   :<javax.persistence.PersistenceException>
<Click to see difference>
_
5
SoCal

彼の答えのためにサムに感謝します。興味深いことに、サムの「ああ、「道」のコメントの1つは、コードで「本当の問題」と思われるものは発見されました。

以下はほとんど機能する最終コード(ほとんど)です。

テストの実行はまだ失敗したため、「ほとんど」、タプルを挿入しようとしたときに制約違反で失敗するように見えます(下記のコンソールログからのスニペットを参照)。

2019-02-27 09:28:50.237  INFO 4860 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: insert into foo (name, id) values (?, ?)
2019-02-27 09:28:50.311  WARN 4860 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23505, SQLState: 23505
2019-02-27 09:28:50.311 ERROR 4860 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Unique index or primary key violation: "UK_A7S9IMMDPCXHLN2D4JHLAY516_INDEX_1 ON PUBLIC.FOO(NAME) VALUES ('0->Test Name', 1)"; SQL statement:
insert into foo (name, id) values (?, ?) [23505-197]
2019-02-27 09:28:50.311  INFO 4860 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@1d296da testClass = FooRepositoryITest, testInstance = com.test.foo.FooRepositoryITest@6989da5e, testMethod = shouldThrowExceptionOnSave@FooRepositoryITest, testException = org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["UK_A7S9IMMDPCXHLN2D4JHLAY516_INDEX_1 ON PUBLIC.FOO(NAME) VALUES ('0->Test Name', 1)"; SQL statement:
insert into foo (name, id) values (?, ?) [23505-197]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement, mergedContextConfiguration = [MergedContextConfiguration@7c7a06ec testClass = FooRepositoryITest, locations = '{}', classes = '{class com.test.foo.FooApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@45018215, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@617263ed, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2f112965, [ImportsContextCustomizer@75d4a5c2 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1f3f4916, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@59d016c9, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["UK_A7S9IMMDPCXHLN2D4JHLAY516_INDEX_1 ON PUBLIC.FOO(NAME) VALUES ('0->Test Name', 1)"; SQL statement:
insert into foo (name, id) values (?, ?) [23505-197]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
 _

しかし、これが「ああ、邪魔になる」コメントが登場する場所です。実際のテストでは失敗していません。それはremovefootuples()のunement inに失敗しているようです。これはshouldthRowExceptionSave()junitによる。しかし、removefootuples()テーブルから既存のタプルを削除しようとするだけで(そして、例外が期待されていない/スローされているテストでは正常にします。 )、コンソールログは「挿入」が試みられていることを示します。

removeFootuples()がコメントアウトされていて、JUnitはテーブルを削除することが許可されている場合、テストは正常に期待された結果で終了します。 testentityManager.flush()inShouldThrowExceptionCave()の( - - == - )このシナリオを避けましたが、...

@DataJpaTest
public class FooRepositoryITest {

    @Autowired
    private TestEntityManager tem;

    @Autowired
    private FooRepository foos;

    private static final int    NUM_ROWS  = 1;
    private static final String BASE_ID   = "->Test Id";
    private static final String BASE_NAME = "->Test Name";

    @BeforeEach
    public void insertFooTuples() {
        Foo foo;

        for (int i = 0; i < NUM_ROWS; i++) {
            foo = new Foo(i + BASE_ID, i + BASE_NAME);
            tem.persist(foo);
        }
        tem.flush();
    }

    /* shouldThrowExceptionOnSave() executes successfully if this
     *  method is commented out
     */
    @AfterEach
    public void removeFooTuples() {
        foos.findAll()
                .forEach(tem::remove);
        tem.flush();
    }

    @Test
    public void shouldThrowExceptionOnSave() {
        String newId   = "New Test Id";
        String newName = "New Test Name";
        Foo    foo     = new Foo(newId, newName);

        foo.setName(foos.findById(0+BASE_ID).get().getName());

        assertThrows(PersistenceException.class, () -> {
                        foos.save(foo);
                        tem.flush();
                    } );
    }
}
 _
0
SoCal