2017-10-13 9 views
6

Ich habe eine Integrations- Test-Klasse für meine UserController. Der Inhalt der folgenden Klasse ist:Transaktionen im Spring-Boot-Test nicht zurückgerollt

// 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(); 
    } 
} 

Wenn ich diese Test-Klasse zweimal ausführen, das zweite Mal es nicht, weil es in der Datenbank mit Benutzername admin (eindeutige Einschränkung) bereits ein Benutzer ist.

Ich teste gegen eine postgres Datenbank, die die gleiche wie in meiner Produktionsumgebung ist. Die Anwendung verwendet Spring jdbcTemplate für Datenbankvorgänge.

Meine Protokollierung produziert die folgenden Protokolle:

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 

Meine Anwendungsfluss ist <request> --> <controller> --> <service with jdbcTemplate> und die Dienste sind Annotation mit @Transactional.

Ich bin wirklich mit diesem fest.

Eine Lösung fand nicht für mich arbeiten, war es eine PlatformTransactionManager Bohne für die Testkonfiguration zu erstellen:

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

Haben Sie 'REQUIRES_NEW' auf Ihrer '@ Transactional'-Methode in Ihrer Serviceebene? – Patrick

+0

@Patrick das funktioniert nicht – mmjmanders

+0

ja, deshalb fragte ich. Dies könnte ein Problem sein. – Patrick

Antwort

4

auf der offiziellen Spring Boot documentation Rollback db Transaktion wird nicht unterstützt Nach wenn Sie es von der direkt bewerben „Web-Schicht“:

Wenn Ihr Test @Transactional ist, wird die Transaktion bei das Ende jeder Testmethode standardmäßig Rollback. Da jedoch die Verwendung dieser Anordnung mit entweder RANDOM_PORT oder DEFINED_PORT implizit eine echte Servlet-Umgebung bietet, werden HTTP-Client und -Server in separaten Threads ausführen, also separate Transaktionen. Jede auf dem Server initiierte Transaktion wird in diesem Fall nicht zurückgesetzt.

Ich schlage vor, Sie folgende Optionen zu prüfen:

  • Verwenden Sie separate Tests für web controller Schicht und database Schicht bei Unit-Tests

  • Erstellen/Wiederherstellen Tabellen vor & Tropfen/Clear sie nach der Ausführung der Testmethode, wenn Integrationstests durchgeführt werden. Dieser Ansatz kann bei einem großen Db-Schema einen erheblichen Mehraufwand verursachen, Sie können Daten jedoch selektiv entsprechend Ihren Anforderungen löschen/löschen.

Verwandte Themen