spring 引导测试中的事务未回滚
Transactions in spring boot testing not rolled back
我的 UserController
有一个集成测试 class。以下class的内容为:
// imports...
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
@Transactional
@Rollback
public class UserControllerTests {
private static final String ENDPOINT = "/v1/users";
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ApplicationProperties applicationProperties;
@Test
public void test_user_create() {
String token = login("test", "test");
HttpEntity<UserRequest> request = createRequest(token, "admin", "admin");
ResponseEntity<User> response = restTemplate.exchange(ENDPOINT, HttpMethod.POST, request, User.class);
assertEquals(HttpStatus.CREATED, response.getStatusCode());
}
private HttpEntity createRequest(String token) {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("Authorization", String.format("Bearer %s", token));
return new HttpEntity(headers);
}
private HttpEntity<UserRequest> createRequest(String token, String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("Authorization", String.format("Bearer %s", token));
return new HttpEntity<>(new UserRequest(username, password), headers);
}
private String login(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("Authorization", String.format("Basic %s", Base64.getEncoder().encodeToString(String.format("%s:%s", applicationProperties.getAuth().getClientId(), applicationProperties.getAuth().getClientSecret()).getBytes())));
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "password");
body.add("username", username);
body.add("password", password);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
ResponseEntity<OAuth2AccessToken> response = restTemplate.exchange("/oauth/token", HttpMethod.POST, request, OAuth2AccessToken.class);
return response.getBody().getValue();
}
}
当我执行此测试 class 两次时,第二次失败,因为数据库中已经有一个用户名为 admin
(唯一约束)的用户。
我正在针对与我的生产环境相同的 postgres
数据库进行测试。该应用程序正在使用 Spring 的 jdbcTemplate
进行数据库操作。
我的日志记录产生了以下日志:
2017-10-13 14:11:31.407 INFO [iam-service,,,] 63566 --- [ main] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context
...
2017-10-13 14:11:32.050 INFO [iam-service,,,] 63566 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test context
我的应用程序流程是 <request> --> <controller> --> <service with jdbcTemplate>
,服务是用 @Transactional
注释的。
我真的被这个困住了。
找到的一个解决方案对我不起作用,它正在为测试配置创建一个 PlatformTransactionManager
bean:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
根据官方 Spring Boot documentation 直接从 "web layer" 应用时不支持数据库事务回滚:
If your test is @Transactional
, it will rollback the transaction at
the end of each test method by default. However, as using this
arrangement with either RANDOM_PORT
or DEFINED_PORT
implicitly
provides a real servlet environment, HTTP client and server will run
in separate threads, thus separate transactions. Any transaction
initiated on the server won’t rollback in this case.
我建议您考虑以下选项:
在单元测试的情况下对 web controller layer and database 层使用单独的测试
Create/Restore 表之前 & Drop/Clear 在执行集成测试时测试方法执行之后。当 Db 模式很大时,这种方法可能会产生很大的开销,但是您可以 clear/restore 根据您的需求有选择地获取数据。
我的 UserController
有一个集成测试 class。以下class的内容为:
// imports...
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
@Transactional
@Rollback
public class UserControllerTests {
private static final String ENDPOINT = "/v1/users";
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private ApplicationProperties applicationProperties;
@Test
public void test_user_create() {
String token = login("test", "test");
HttpEntity<UserRequest> request = createRequest(token, "admin", "admin");
ResponseEntity<User> response = restTemplate.exchange(ENDPOINT, HttpMethod.POST, request, User.class);
assertEquals(HttpStatus.CREATED, response.getStatusCode());
}
private HttpEntity createRequest(String token) {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("Authorization", String.format("Bearer %s", token));
return new HttpEntity(headers);
}
private HttpEntity<UserRequest> createRequest(String token, String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("Authorization", String.format("Bearer %s", token));
return new HttpEntity<>(new UserRequest(username, password), headers);
}
private String login(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("Authorization", String.format("Basic %s", Base64.getEncoder().encodeToString(String.format("%s:%s", applicationProperties.getAuth().getClientId(), applicationProperties.getAuth().getClientSecret()).getBytes())));
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "password");
body.add("username", username);
body.add("password", password);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
ResponseEntity<OAuth2AccessToken> response = restTemplate.exchange("/oauth/token", HttpMethod.POST, request, OAuth2AccessToken.class);
return response.getBody().getValue();
}
}
当我执行此测试 class 两次时,第二次失败,因为数据库中已经有一个用户名为 admin
(唯一约束)的用户。
我正在针对与我的生产环境相同的 postgres
数据库进行测试。该应用程序正在使用 Spring 的 jdbcTemplate
进行数据库操作。
我的日志记录产生了以下日志:
2017-10-13 14:11:31.407 INFO [iam-service,,,] 63566 --- [ main] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context
...
2017-10-13 14:11:32.050 INFO [iam-service,,,] 63566 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test context
我的应用程序流程是 <request> --> <controller> --> <service with jdbcTemplate>
,服务是用 @Transactional
注释的。
我真的被这个困住了。
找到的一个解决方案对我不起作用,它正在为测试配置创建一个 PlatformTransactionManager
bean:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
根据官方 Spring Boot documentation 直接从 "web layer" 应用时不支持数据库事务回滚:
If your test is
@Transactional
, it will rollback the transaction at the end of each test method by default. However, as using this arrangement with eitherRANDOM_PORT
orDEFINED_PORT
implicitly provides a real servlet environment, HTTP client and server will run in separate threads, thus separate transactions. Any transaction initiated on the server won’t rollback in this case.
我建议您考虑以下选项:
在单元测试的情况下对 web controller layer and database 层使用单独的测试
Create/Restore 表之前 & Drop/Clear 在执行集成测试时测试方法执行之后。当 Db 模式很大时,这种方法可能会产生很大的开销,但是您可以 clear/restore 根据您的需求有选择地获取数据。