春季开机测试未回滚的交易

问题描述:

我有我的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> --> <service with jdbcTemplate>和服务批注与@Transactional

我真的很坚持这一点。

一个解决方案发现我没有工作,这是创造一个PlatformTransactionManager豆的测试配置:

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

你的'@ Transactional'方法在你的服务层有'REQUIRES_NEW'吗? – Patrick

+0

@帕特里克这不起作用 – mmjmanders

+0

是的,这就是为什么我问。这可能是一个问题。 – Patrick

根据官方Spring Boot documentation数据库事务回滚时,直接从应用它不支持“网络层”:

如果测试是@Transactional,它将在 默认每个测试方法的末尾回滚事务。然而,由于使用这种 安排与RANDOM_PORTDEFINED_PORT暗含 提供了一个真正的servlet环境,HTTP客户端和服务器将在单独的线程中运行 ,因此单独的事务。在这种情况下,服务器上启动的任何事务 都不会回滚。

我建议你考虑以下选项:

  • 使用单独试验web controller层和database层的单元测试的情况下

  • 创建/前&删除/清除恢复表他们在执行集成测试时执行测试方法之后。当Db模式较大时,此方法可能会产生大量开销,但您可以根据需要选择性地清除/恢复数据。