Spring Boot测试中的事务未回滚

15

我有一个用于UserController的集成测试类。以下是该类的内容:

// 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();
    }
}
当我执行这个测试类两次时,第二次会失败,因为数据库中已经有一个用户名为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> --> <使用jdbcTemplate的服务>,并且这些服务都注释了@Transactional

我真的被卡住了。

我找到的一个解决方案对我没有用,它是在测试配置中创建一个PlatformTransactionManager bean:

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

1
你的服务层的@Transactional方法上有使用REQUIRES_NEW吗? - Patrick
@Patrick 这个不起作用。 - mmjmanders
是的,这就是我问的原因。这可能会成为一个问题。 - Patrick
@mmjmanders,你是否在公共代码库中发布了这个项目,或者能否提供一个独立的示例来重现这个错误? - Alex Saunin
1
@AlexSaunin 我已经创建了一个:https://github.com/mmjmanders/spring-boot-transactional-test - mmjmanders
1个回答

37
根据官方的 Spring Boot 文档,直接从 "Web 层" 应用数据库事务回滚不被支持:

如果您的测试是 @Transactional 的话,每个测试方法默认情况下都会在结束时回滚事务。但是,如果使用此设置与 RANDOM_PORTDEFINED_PORT,则会隐式提供真实的 Servlet 环境,HTTP 客户端和服务器将在单独的线程中运行,因此是分开的事务。在这种情况下,在服务器上启动的任何事务都不会回滚。

我建议您考虑以下选项:
  • 在进行单元测试时,为 Web 控制器 层和 数据库 层使用单独的测试。

  • 在执行集成测试时,在测试方法执行之前创建/恢复表,在执行之后删除/清除它们。当 Db 架构较大时,此方法可能会有很大的开销,但您可以根据需求选择清除/恢复数据。


2
有没有办法在彼此之间共享事务上下文? - Vince V.
如果数据库也被其他客户端使用,或者是一个低环境的真实数据库,那么第二个选项实际上不能被考虑。一个选项是在Before Each中创建所有需要的实体,并在After Each中删除它们。 - Daniel Pop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接