来自 @Transactional 测试的数据在测试之间持续存在
Data from test with @Transactional are persisting between tests
我有个小问题。我已经开始为我的小项目编写测试。
该项目使用 SpringBoot,来自 Spring 的标准 JpaRepository 作为测试框架,我正在使用 Spock,为了测试数据库,我正在使用来自 TestContainers 的 PostgreSQL 容器。
问题是,尽管每个测试都有 @Transacional,但测试之间的数据仍然存在。
最奇怪的是,在我看到的日志中,该事务已回滚。
如果有任何帮助,我将不胜感激。
所以,这些是文件:
- 具有用于测试的共享容器的文件,所有集成测试都应从以下扩展:
@SpringBootTest(classes = DatabaseMain.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class BaseTestIT extends Specification {
private static final Logger LOG = LoggerFactory.getLogger(BaseTestIT.class);
@Shared
private static final PostgreSQLContainer POSTGRES_CONTAINER = new PostgreSQLContainer("postgres")
.withDatabaseName('test').withUsername(
'test').withPassword('test');
...
}
- 配置文件:
@Configuration
@PropertySource("database.properties")
public class ConfigurationIT {
private final String databaseUrl;
private final String databaseDialect;
private final String databaseType;
@Autowired
public ConfigurationIT(@Value("${test.database.url}") String databaseUrl,
@Value("${test.database.dialect}") String databaseDialect,
@Value("${test.database.type}") String databaseType) {
this.databaseDialect = databaseDialect;
this.databaseUrl = databaseUrl;
this.databaseType = databaseType;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
entityManagerFactoryBean.setPersistenceUnitName("testIT");
entityManagerFactoryBean.setPackagesToScan("com.testproject");
Map<String, String> properties = new HashMap<>();
properties.put("spring.jpa.database", this.databaseType);
properties.put("spring.jpa.database-platform", this.databaseDialect);
properties.put("hibernate.dialect", this.databaseDialect);
properties.put("hibernate.connection.url", this.databaseUrl);
entityManagerFactoryBean.setJpaPropertyMap(properties);
return entityManagerFactoryBean;
}
@Bean
@Autowired
public JpaTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf.getObject());
return transactionManager;
}
}
- application.yaml
spring:
datasource:
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
url: ${test.database.url}
username: ${test.database.username}
password: ${test.database.password}
jpa:
database-platform: ${test.database.dialect}
database: ${test.database.type}
- 和测试文件:
@SpringBootTest(classes = DatabaseMain.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Testcontainers
class ArchitectTestIT extends BaseTestIT {
private static final String NAME = "name";
private static final String LAST_NAME = "last_name";
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ArchitectRepository architectRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Creating Architect with proper DTO should return created architect and 201 code"() {
given: "proper architect dto"
ArchitectBasicDto architectBasicDto = new ArchitectBasicDto();
architectBasicDto.setFirstName(NAME);
architectBasicDto.setLastName(LAST_NAME);
HttpEntity entity = new HttpEntity(architectBasicDto);
when: "passed to architect post method"
def exchange = this.restTemplate.exchange("/architects", HttpMethod.POST, entity, ArchitectBasicDto.class);
then: "returns code 201 with dto of created Architected with location in the header"
exchange.statusCode == HttpStatus.CREATED;
and:
exchange.getBody().getFirstName() == NAME;
and:
exchange.getBody().getLastName() == LAST_NAME;
and:
def id = exchange.getBody().getId()
id != null;
and:
exchange.getHeaders().get("Location").get(0) == "/architects/" + id;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Creating Architect with not proper DTO should return code 400 and not create new Architect"() {
given:
ArchitectBasicDto architectBasicDto = new ArchitectBasicDto();
architectBasicDto.setFirstName(null);
architectBasicDto.setLastName(LAST_NAME);
HttpEntity entity = new HttpEntity(architectBasicDto);
when:
def exchange = this.restTemplate.exchange("/architects", HttpMethod.POST, entity, ArchitectBasicDto.class);
then:
exchange.statusCode == HttpStatus.BAD_REQUEST;
and:
exchange.getBody().getId() == null;
amd:
exchange.getBody().getFirstName() == null;
and:
exchange.getBody().getLastName() == null;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Removing existing Architect will return code 200 and remove it from database"() {
given:
ArchitectBasicDto architectBasicDto5 = new ArchitectBasicDto();
architectBasicDto5.setFirstName(NAME);
architectBasicDto5.setLastName(LAST_NAME);
HttpEntity entity5 = new HttpEntity(architectBasicDto5);
ResponseEntity<ArchitectBasicDto> exchange5 =
this.restTemplate.exchange("/architects", HttpMethod.POST, entity5, ArchitectBasicDto.class);
HttpEntity entity = new HttpEntity(null);
when:
ResponseEntity<ArchitectBasicDto> exchange =
this.restTemplate.exchange("/architects/" + exchange5.getBody().getId(), HttpMethod.DELETE, entity,
ArchitectBasicDto.class);
then:
exchange.statusCode == HttpStatus.OK;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Removing not existing Architect will give code 400 and error message about not existing Architect"() {
given:
HttpEntity entity = new HttpEntity(null);
when:
ResponseEntity<Map<String, String>> exchange =
this.restTemplate.exchange("/architects/" + 2000, HttpMethod.DELETE, entity,
Map<String, String>.class);
then:
exchange.statusCode == HttpStatus.BAD_REQUEST;
and:
exchange.getBody().get("message") == "Architect with id 2,000 does not exist.";
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Calling get on /architects should return all architects basic dto"() {
given:
HttpEntity entity = new HttpEntity(null);
when:
ResponseEntity<List<ArchitectBasicDto>> exchange =
this.restTemplate.exchange("/architects", HttpMethod.GET, entity,
List<ArchitectBasicDto>.class);
then:
exchange.getBody().size() == 0;
}
}
上次测试稍微修改了一下,只是为了说明问题。由于我没有保存任何实体,它应该通过,但没有:
Condition not satisfied:
exchange.getBody().size() == 0
| | | |
| | 1 false
| [[id:100, firstName:name, lastName:last_name]]
<200,[{id=100, firstName=name, lastName=last_name}],[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Thu, 16 Dec 2021 20:55:38 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>
至于日志,它们显示事务正在回滚:
[2021-12-16T21:55:38,337] INFO [main][] - TransactionContext.startTransaction(TransactionContext.java:107) - Began transaction (1) for test context [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@5a30ab46, testMethod = $spock_feature_1_0@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4df4f611]; rollback [true]
[2021-12-16T21:55:38,589] INFO [http-nio-8080-exec-1][] - DirectJDKLog.log(DirectJDKLog.java:173) - Initializing Spring DispatcherServlet 'dispatcherServlet'
[2021-12-16T21:55:38,589] INFO [http-nio-8080-exec-1][] - FrameworkServlet.initServletBean(FrameworkServlet.java:525) - Initializing Servlet 'dispatcherServlet'
[2021-12-16T21:55:38,591] INFO [http-nio-8080-exec-1][] - FrameworkServlet.initServletBean(FrameworkServlet.java:547) - Completed initialization in 2 ms
[2021-12-16T21:55:38,745] INFO [main][] - TransactionContext.endTransaction(TransactionContext.java:139) - Rolled back transaction for test: [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@5a30ab46, testMethod = $spock_feature_1_0@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
[2021-12-16T21:55:38,749] INFO [main][] - TransactionContext.startTransaction(TransactionContext.java:107) - Began transaction (1) for test context [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@317e9cf5, testMethod = $spock_feature_1_1@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4df4f611]; rollback [true]
[2021-12-16T21:55:38,759] ERROR [http-nio-8080-exec-2][] - GlobalExceptionHandler.handleException(GlobalExceptionHandler.java:33) - isNull.architect.firstName
com.testproject.task.sharedkernel.exceptions.IllegalArgumentException: isNull.architect.firstName
at com.testproject.task.sharedkernel.exceptions.BaseValidator.assertIsTrue(BaseValidator.java:36) ~[classes/:?]
at com.testproject.task.architect.application.ArchitectValidator.validateName(ArchitectValidator.java:50) ~[classes/:?]
at com.testproject.task.architect.application.ArchitectValidator.validateBasicArchitectDto(ArchitectValidator.java:38) ~[classes/:?]
at com.testproject.task.architect.application.impl.ArchitectApplicationServiceImpl.createArchitect(ArchitectApplicationServiceImpl.java:41) ~[classes/:?]
at com.testproject.task.architect.application.impl.ArchitectApplicationServiceImpl$$FastClassBySpringCGLIB$$e4b5b2ac.invoke(<generated>) ~[classes/:?]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.8.jar:5.3.8]
at com.testproject.task.architect.application.impl.ArchitectApplicationServiceImpl$$EnhancerBySpringCGLIB$$e31df751.createArchitect(<generated>) ~[classes/:?]
at com.testproject.task.architect.rest.ArchitectRestController.createArchitect(ArchitectRestController.java:36) ~[classes/:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
...
at java.lang.Thread.run(Thread.java:832) [?:?]
[2021-12-16T21:55:38,810] INFO [main][] - TransactionContext.endTransaction(TransactionContext.java:139) - Rolled back transaction for test: [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@317e9cf5, testMethod = $spock_feature_1_1@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
[2021-12-16T21:55:38,818] INFO [main][] - TransactionContext.startTransaction(TransactionContext.java:107) - Began transaction (1) for test context [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@9b8d3db, testMethod = $spock_feature_1_2@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4df4f611]; rollback [true]
[2021-12-16T21:55:39,054] INFO [main][] - TransactionContext.endTransaction(TransactionContext.java:139) - Rolled back transaction for test: [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@9b8d3db, testMethod = $spock_feature_1_2@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
所以我有点卡住了,至于那些实际上没有被回滚的原因可能是什么,我将不胜感激任何帮助。谢谢!
您的测试会启动一个侦听真实端口的应用。并且您使用 TestRestTemplate 进行 HTTP 调用。这就像您 运行 从远程计算机进行测试一样 - 您是否希望 @Transactional
对此类测试应用某种应用程序?
@Transactional
仅当您直接调用您的端点时才有效,没有任何网络调用:
- 将端点对象直接注入测试并调用其方法
- 或使用 MockMvc(或 RestAssured+MockMvc)——它最终也会直接调用端点
这两个选项都将简化调试 - 您将能够在调用堆栈中看到当前哪个测试正在调用您的生产代码。
PS:在测试运行之间保存数据也不应该成为问题。您可以使用 randomization.
隔离您的测试
我有个小问题。我已经开始为我的小项目编写测试。 该项目使用 SpringBoot,来自 Spring 的标准 JpaRepository 作为测试框架,我正在使用 Spock,为了测试数据库,我正在使用来自 TestContainers 的 PostgreSQL 容器。 问题是,尽管每个测试都有 @Transacional,但测试之间的数据仍然存在。 最奇怪的是,在我看到的日志中,该事务已回滚。 如果有任何帮助,我将不胜感激。
所以,这些是文件:
- 具有用于测试的共享容器的文件,所有集成测试都应从以下扩展:
@SpringBootTest(classes = DatabaseMain.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class BaseTestIT extends Specification {
private static final Logger LOG = LoggerFactory.getLogger(BaseTestIT.class);
@Shared
private static final PostgreSQLContainer POSTGRES_CONTAINER = new PostgreSQLContainer("postgres")
.withDatabaseName('test').withUsername(
'test').withPassword('test');
...
}
- 配置文件:
@Configuration
@PropertySource("database.properties")
public class ConfigurationIT {
private final String databaseUrl;
private final String databaseDialect;
private final String databaseType;
@Autowired
public ConfigurationIT(@Value("${test.database.url}") String databaseUrl,
@Value("${test.database.dialect}") String databaseDialect,
@Value("${test.database.type}") String databaseType) {
this.databaseDialect = databaseDialect;
this.databaseUrl = databaseUrl;
this.databaseType = databaseType;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
entityManagerFactoryBean.setPersistenceUnitName("testIT");
entityManagerFactoryBean.setPackagesToScan("com.testproject");
Map<String, String> properties = new HashMap<>();
properties.put("spring.jpa.database", this.databaseType);
properties.put("spring.jpa.database-platform", this.databaseDialect);
properties.put("hibernate.dialect", this.databaseDialect);
properties.put("hibernate.connection.url", this.databaseUrl);
entityManagerFactoryBean.setJpaPropertyMap(properties);
return entityManagerFactoryBean;
}
@Bean
@Autowired
public JpaTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf.getObject());
return transactionManager;
}
}
- application.yaml
spring:
datasource:
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
url: ${test.database.url}
username: ${test.database.username}
password: ${test.database.password}
jpa:
database-platform: ${test.database.dialect}
database: ${test.database.type}
- 和测试文件:
@SpringBootTest(classes = DatabaseMain.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Testcontainers
class ArchitectTestIT extends BaseTestIT {
private static final String NAME = "name";
private static final String LAST_NAME = "last_name";
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ArchitectRepository architectRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Creating Architect with proper DTO should return created architect and 201 code"() {
given: "proper architect dto"
ArchitectBasicDto architectBasicDto = new ArchitectBasicDto();
architectBasicDto.setFirstName(NAME);
architectBasicDto.setLastName(LAST_NAME);
HttpEntity entity = new HttpEntity(architectBasicDto);
when: "passed to architect post method"
def exchange = this.restTemplate.exchange("/architects", HttpMethod.POST, entity, ArchitectBasicDto.class);
then: "returns code 201 with dto of created Architected with location in the header"
exchange.statusCode == HttpStatus.CREATED;
and:
exchange.getBody().getFirstName() == NAME;
and:
exchange.getBody().getLastName() == LAST_NAME;
and:
def id = exchange.getBody().getId()
id != null;
and:
exchange.getHeaders().get("Location").get(0) == "/architects/" + id;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Creating Architect with not proper DTO should return code 400 and not create new Architect"() {
given:
ArchitectBasicDto architectBasicDto = new ArchitectBasicDto();
architectBasicDto.setFirstName(null);
architectBasicDto.setLastName(LAST_NAME);
HttpEntity entity = new HttpEntity(architectBasicDto);
when:
def exchange = this.restTemplate.exchange("/architects", HttpMethod.POST, entity, ArchitectBasicDto.class);
then:
exchange.statusCode == HttpStatus.BAD_REQUEST;
and:
exchange.getBody().getId() == null;
amd:
exchange.getBody().getFirstName() == null;
and:
exchange.getBody().getLastName() == null;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Removing existing Architect will return code 200 and remove it from database"() {
given:
ArchitectBasicDto architectBasicDto5 = new ArchitectBasicDto();
architectBasicDto5.setFirstName(NAME);
architectBasicDto5.setLastName(LAST_NAME);
HttpEntity entity5 = new HttpEntity(architectBasicDto5);
ResponseEntity<ArchitectBasicDto> exchange5 =
this.restTemplate.exchange("/architects", HttpMethod.POST, entity5, ArchitectBasicDto.class);
HttpEntity entity = new HttpEntity(null);
when:
ResponseEntity<ArchitectBasicDto> exchange =
this.restTemplate.exchange("/architects/" + exchange5.getBody().getId(), HttpMethod.DELETE, entity,
ArchitectBasicDto.class);
then:
exchange.statusCode == HttpStatus.OK;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Removing not existing Architect will give code 400 and error message about not existing Architect"() {
given:
HttpEntity entity = new HttpEntity(null);
when:
ResponseEntity<Map<String, String>> exchange =
this.restTemplate.exchange("/architects/" + 2000, HttpMethod.DELETE, entity,
Map<String, String>.class);
then:
exchange.statusCode == HttpStatus.BAD_REQUEST;
and:
exchange.getBody().get("message") == "Architect with id 2,000 does not exist.";
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
def "Calling get on /architects should return all architects basic dto"() {
given:
HttpEntity entity = new HttpEntity(null);
when:
ResponseEntity<List<ArchitectBasicDto>> exchange =
this.restTemplate.exchange("/architects", HttpMethod.GET, entity,
List<ArchitectBasicDto>.class);
then:
exchange.getBody().size() == 0;
}
}
上次测试稍微修改了一下,只是为了说明问题。由于我没有保存任何实体,它应该通过,但没有:
Condition not satisfied:
exchange.getBody().size() == 0
| | | |
| | 1 false
| [[id:100, firstName:name, lastName:last_name]]
<200,[{id=100, firstName=name, lastName=last_name}],[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Thu, 16 Dec 2021 20:55:38 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>
至于日志,它们显示事务正在回滚:
[2021-12-16T21:55:38,337] INFO [main][] - TransactionContext.startTransaction(TransactionContext.java:107) - Began transaction (1) for test context [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@5a30ab46, testMethod = $spock_feature_1_0@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4df4f611]; rollback [true]
[2021-12-16T21:55:38,589] INFO [http-nio-8080-exec-1][] - DirectJDKLog.log(DirectJDKLog.java:173) - Initializing Spring DispatcherServlet 'dispatcherServlet'
[2021-12-16T21:55:38,589] INFO [http-nio-8080-exec-1][] - FrameworkServlet.initServletBean(FrameworkServlet.java:525) - Initializing Servlet 'dispatcherServlet'
[2021-12-16T21:55:38,591] INFO [http-nio-8080-exec-1][] - FrameworkServlet.initServletBean(FrameworkServlet.java:547) - Completed initialization in 2 ms
[2021-12-16T21:55:38,745] INFO [main][] - TransactionContext.endTransaction(TransactionContext.java:139) - Rolled back transaction for test: [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@5a30ab46, testMethod = $spock_feature_1_0@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
[2021-12-16T21:55:38,749] INFO [main][] - TransactionContext.startTransaction(TransactionContext.java:107) - Began transaction (1) for test context [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@317e9cf5, testMethod = $spock_feature_1_1@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4df4f611]; rollback [true]
[2021-12-16T21:55:38,759] ERROR [http-nio-8080-exec-2][] - GlobalExceptionHandler.handleException(GlobalExceptionHandler.java:33) - isNull.architect.firstName
com.testproject.task.sharedkernel.exceptions.IllegalArgumentException: isNull.architect.firstName
at com.testproject.task.sharedkernel.exceptions.BaseValidator.assertIsTrue(BaseValidator.java:36) ~[classes/:?]
at com.testproject.task.architect.application.ArchitectValidator.validateName(ArchitectValidator.java:50) ~[classes/:?]
at com.testproject.task.architect.application.ArchitectValidator.validateBasicArchitectDto(ArchitectValidator.java:38) ~[classes/:?]
at com.testproject.task.architect.application.impl.ArchitectApplicationServiceImpl.createArchitect(ArchitectApplicationServiceImpl.java:41) ~[classes/:?]
at com.testproject.task.architect.application.impl.ArchitectApplicationServiceImpl$$FastClassBySpringCGLIB$$e4b5b2ac.invoke(<generated>) ~[classes/:?]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.8.jar:5.3.8]
at com.testproject.task.architect.application.impl.ArchitectApplicationServiceImpl$$EnhancerBySpringCGLIB$$e31df751.createArchitect(<generated>) ~[classes/:?]
at com.testproject.task.architect.rest.ArchitectRestController.createArchitect(ArchitectRestController.java:36) ~[classes/:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
...
at java.lang.Thread.run(Thread.java:832) [?:?]
[2021-12-16T21:55:38,810] INFO [main][] - TransactionContext.endTransaction(TransactionContext.java:139) - Rolled back transaction for test: [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@317e9cf5, testMethod = $spock_feature_1_1@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
[2021-12-16T21:55:38,818] INFO [main][] - TransactionContext.startTransaction(TransactionContext.java:107) - Began transaction (1) for test context [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@9b8d3db, testMethod = $spock_feature_1_2@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4df4f611]; rollback [true]
[2021-12-16T21:55:39,054] INFO [main][] - TransactionContext.endTransaction(TransactionContext.java:139) - Rolled back transaction for test: [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@9b8d3db, testMethod = $spock_feature_1_2@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
所以我有点卡住了,至于那些实际上没有被回滚的原因可能是什么,我将不胜感激任何帮助。谢谢!
您的测试会启动一个侦听真实端口的应用。并且您使用 TestRestTemplate 进行 HTTP 调用。这就像您 运行 从远程计算机进行测试一样 - 您是否希望 @Transactional
对此类测试应用某种应用程序?
@Transactional
仅当您直接调用您的端点时才有效,没有任何网络调用:
- 将端点对象直接注入测试并调用其方法
- 或使用 MockMvc(或 RestAssured+MockMvc)——它最终也会直接调用端点
这两个选项都将简化调试 - 您将能够在调用堆栈中看到当前哪个测试正在调用您的生产代码。
PS:在测试运行之间保存数据也不应该成为问题。您可以使用 randomization.
隔离您的测试