Spring 如果@Transactional,引导集成测试失败
Spring Boot integration test fails if @Transactional
我有一个简单的 Spring 启动应用程序,它使用 Spring 数据 JDBC 存储库并公开 REST API。除了我的集成测试,一切似乎都 运行 OK。请参阅下面的存储库配置摘录。
@Configuration
@EnableJdbcRepositories
class RepositoryConfig : AbstractJdbcConfiguration() {
@Bean
@Profile("int-test")
fun intTestDataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("sql/h2/schema_create.sql")
.build()
}
@Bean
fun namedParameterJdbcOperations(dataSource: DataSource): NamedParameterJdbcOperations {
return NamedParameterJdbcTemplate(dataSource)
}
@Bean
fun transactionManager(dataSource: DataSource): TransactionManager {
return DataSourceTransactionManager(dataSource)
}
}
集成测试如下。
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ActiveProfiles("int-test")
@Transactional
class OrganizationControllerIntTest {
@Autowired
private lateinit var organizationRepository: OrganizationRepository
@Autowired
private lateinit var restTemplate: TestRestTemplate
@Autowired
private lateinit var testDataGenerator: TestDataGenerator
@BeforeEach
fun beforeEach() {
val organizations = testDataGenerator.organizations(10).toList()
organizationRepository.saveAll(organizations)
}
@Test
fun `When GET organizations then return all organizations`() {
val result = restTemplate.getForEntity("/organizations", String::class.java)
assertThat(result?.statusCode, equalTo(HttpStatus.OK))
assertThat(result?.body, containsString("items"))
}
}
如果不应用@Transactional
注释,则测试成功。因为我需要在每次测试后回滚更改,所以我想让测试成为事务性的。问题是,在那种情况下,响应不包含任何项目,比如数据库将是空的,我不知道为什么。
使用 @Transactional
进行系统测试将不起作用。
您的 @BeforeEach
方法中的数据被插入到数据库中 但是 事务从未提交。数据仅在同一事务内可见。
现在您向服务器发送一个 HTTP 请求,服务器打开一个新的数据库连接并开始一个新的事务。但是,由于未提交来自其他事务的数据,因此它不会看到任何内容。
@Transactionl
仅在使用 MockMvc
时有效,因为这将 运行 在同一事务和线程中。实际发出的 HTTP 请求并非如此。
对每个测试都使用数据设置,尽管在进行全系统测试时应谨慎使用!你要么:
- 如果您真的想要系统测试,请以无所谓的方式编写测试。
- 使用
MockMvc
来 运行 您的测试,而不是实际的 HTTP 请求,这将使其(重新)使用相同的事务。
- 不要编写系统测试,而是使用
@WebMvcTest
来测试您的控制器
我有一个简单的 Spring 启动应用程序,它使用 Spring 数据 JDBC 存储库并公开 REST API。除了我的集成测试,一切似乎都 运行 OK。请参阅下面的存储库配置摘录。
@Configuration
@EnableJdbcRepositories
class RepositoryConfig : AbstractJdbcConfiguration() {
@Bean
@Profile("int-test")
fun intTestDataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("sql/h2/schema_create.sql")
.build()
}
@Bean
fun namedParameterJdbcOperations(dataSource: DataSource): NamedParameterJdbcOperations {
return NamedParameterJdbcTemplate(dataSource)
}
@Bean
fun transactionManager(dataSource: DataSource): TransactionManager {
return DataSourceTransactionManager(dataSource)
}
}
集成测试如下。
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ActiveProfiles("int-test")
@Transactional
class OrganizationControllerIntTest {
@Autowired
private lateinit var organizationRepository: OrganizationRepository
@Autowired
private lateinit var restTemplate: TestRestTemplate
@Autowired
private lateinit var testDataGenerator: TestDataGenerator
@BeforeEach
fun beforeEach() {
val organizations = testDataGenerator.organizations(10).toList()
organizationRepository.saveAll(organizations)
}
@Test
fun `When GET organizations then return all organizations`() {
val result = restTemplate.getForEntity("/organizations", String::class.java)
assertThat(result?.statusCode, equalTo(HttpStatus.OK))
assertThat(result?.body, containsString("items"))
}
}
如果不应用@Transactional
注释,则测试成功。因为我需要在每次测试后回滚更改,所以我想让测试成为事务性的。问题是,在那种情况下,响应不包含任何项目,比如数据库将是空的,我不知道为什么。
使用 @Transactional
进行系统测试将不起作用。
您的 @BeforeEach
方法中的数据被插入到数据库中 但是 事务从未提交。数据仅在同一事务内可见。
现在您向服务器发送一个 HTTP 请求,服务器打开一个新的数据库连接并开始一个新的事务。但是,由于未提交来自其他事务的数据,因此它不会看到任何内容。
@Transactionl
仅在使用 MockMvc
时有效,因为这将 运行 在同一事务和线程中。实际发出的 HTTP 请求并非如此。
对每个测试都使用数据设置,尽管在进行全系统测试时应谨慎使用!你要么:
- 如果您真的想要系统测试,请以无所谓的方式编写测试。
- 使用
MockMvc
来 运行 您的测试,而不是实际的 HTTP 请求,这将使其(重新)使用相同的事务。 - 不要编写系统测试,而是使用
@WebMvcTest
来测试您的控制器