Я подумал назвать это «Следствием неопределенности Гейзенберга для исключений Java», но это было (а) слишком громоздко и (б) недостаточно описательно.
BLUF: я пытаюсь поймать, втест JUnit 5 против приложения Spring Boot, исключение выдается, когда кортеж сохраняется в таблице базы данных с нарушением ограничения (дублирующее значение в столбце, помеченном как «уникальный»).Я могу поймать исключение в блоке try-catch, но не использую «assertThrows ()» JUnit.
Elaboration
Для простоты репликации я сузил свой код досущность и репозиторий, а также два теста (один работает, другой является причиной этого поста).Также для простоты репликации я использую H2 в качестве базы данных.
Я прочитал, что существуют потенциальные проблемы с областью транзакции, которые могут привести к тому, что исключение, генерируемое ограничением, не будет выброшено в пределах области вызоваметод.Я подтвердил это простым блоком try-catch вокруг оператора «foos.aave (foo);» в shouldThrowExceptionOnSave () (без оператора «tem.flush ()»).
Я решил использовать TestEntityManager.flush () заставил транзакцию зафиксировать / завершить и смог успешно перехватить исключение в блоке try-catch.Однако это было не ожидаемое DataIntegrityViolationException, а PersistenceException.
Я попытался использовать аналогичный механизм (т. Е. Использовать TestEntityManager.flush (), чтобы вызвать проблему в операторе assertThrows (). Но «нет радости»).”.
Когда я пытаюсь« assertThrows (PersistenceException.class,… », метод завершается с DataIntegrityViolationException.
Когда я пытаюсь« assertThrows (DataIntegrityViolationException.class,… », я на самом деле получаюсообщение об ошибке JUnit, указывающее на то, что ожидаемое исключение DataIntegrityViolationException не соответствует фактическому исключению, а именно… javax.persistence.PersistenceException!
Любая помощь / понимание будут высоко оценены.
Добавить примечание: Блок try-catch в shouldThrowExceptionOnSave () предназначен только для того, чтобы увидеть, какое исключение перехвачено.
Класс сущности
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()
}
Вывод с«assertThrows (PersitenceException, ...)»
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>