Search code examples
spring-bootintegration-testingspockspring-transactionsspring-test

Integration Flow Test Spring Transaction


I'm currently writing a Spock integration test for my Spring application.

I'd like to use @Stepwise in order to perform a test which interacts with the database and then have the next test build on top of the data left behind from the first test.

Unfortunately it seems that a new transaction is started for every test method, thus clearing the data I need to build upon. Rollback(false) does not prevent this behaviour, since the whole transaction is discarded AFAIK.

Here's an example, the MyUserService interacts with a @Repository-interface.

@Stepwise
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
class MyServiceImplIntegrationFlowSpec extends Specification {

  @Autowired
  @Subject
  MyUserService myUserService

  @Shared
  String userId

  void "create user"() {

    when:
    userId = myUserService.createUser()

    then:
    userId
  }

  void "change user permission"() {

    when:
    myUserService.changePermission(userId, "read")

    then:
    myUserService.fetchPermission() == "read"
  }
}

How can I reuse the data which was created by the previous test method, as is commonly done with @Stepwise, in conjunction with database operations?


Solution

  • The Spring Test framework rolls back the data of each test method by default. You can change this default behaviour by adding the @Commit annotation to each of your test methods where you want to keep the changes in the database. If the whole test suite should commit data to the database I think you can put the @Commit annotation also on class level.

    See the reference https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testing-tx

    It says:

    One common issue in tests that access a real database is their effect on the state of the persistence store. Even when you use a development database, changes to the state may affect future tests. Also, many operations — such as inserting or modifying persistent data — cannot be performed (or verified) outside of a transaction.

    and continues describing with

    The TestContext framework addresses this issue. By default, the framework creates and rolls back a transaction for each test. You can write code that can assume the existence of a transaction. If you call transactionally proxied objects in your tests, they behave correctly, according to their configured transactional semantics. In addition, if a test method deletes the contents of selected tables while running within the transaction managed for the test, the transaction rolls back by default, and the database returns to its state prior to execution of the test. Transactional support is provided to a test by using a PlatformTransactionManager bean defined in the test’s application context.

    If you want a transaction to commit (unusual, but occasionally useful when you want a particular test to populate or modify the database), you can tell the TestContext framework to cause the transaction to commit instead of roll back by using the @Commit annotation.

    Your test case could look like

    @Stepwise
    @SpringBootTest
    @TestPropertySource(locations = "classpath:application-test.properties")
    @Commit // if you want all test methods to commit to the database
    class MyServiceImplIntegrationFlowSpec extends Specification {
    
      @Autowired
      @Subject
      MyUserService myUserService
    
      @Shared
      String userId
    
      @Commit // if a single test needs to commit to the database
      void "create user"() {
    
        when:
        userId = myUserService.createUser()
    
        then:
        userId
      }
    
      void "change user permission"() {
    
        when:
        myUserService.changePermission(userId, "read")
    
        then:
        myUserService.fetchPermission() == "read"
      }
    }