spring boot测试中的事务未回滚

mmj*_*ers 6 postgresql junit spring spring-jdbc spring-boot

我有一个集成测试课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();
    }
}
Run Code Online (Sandbox Code Playgroud)

当我执行此测试类两次时,第二次失败,因为数据库中已有一个用户名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 
Run Code Online (Sandbox Code Playgroud)

我的应用程序流程是<request> --> <controller> --> <service with jdbcTemplate>,服务是注释@Transactional.

我真的很困惑.

发现一个解决方案对我不起作用,它是PlatformTransactionManager为测试配置创建一个bean:

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
Run Code Online (Sandbox Code Playgroud)

Ale*_*nin 8

根据官方的Spring Boot文档,当您直接从"web层"应用它时,不支持db transaction rollback:

如果您的测试是@Transactional,它将默认在每个测试方法结束时回滚事务.但是,由于使用这种安排RANDOM_PORTDEFINED_PORT隐式提供真正的servlet环境,HTTP客户端和服务器将在单独的线程中运行,从而分离事务.在这种情况下,在服务器上启动的任何事务都不会回滚.

我建议你考虑以下几种选择:

  • 在单元测试的情况下,对Web控制器层和数据库层使用单独的测试

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

  • 有没有办法在彼此之间共享事务上下文? (2认同)