-->

Grails 3.0.9: Spock integration tests' @Rollba

2019-05-23 09:05发布

问题:

I'm currently working on some integration (or functional, I'm not really a QA but back-end dev, so I might be sloppy with terms) REST tests for our project, we are using Grails 3.0.9, Spock Framework 1.0-Groovy-2.4 and PostgreSQL DB for testing.

Also we have separate DB for testing purposes, it is still crucial that changes are rolled back after each test. I have looked through Grails testing doc and was trying to use @Rollback annotation as described in examples - and it is just not working, changes are still committed to DB.

As it is work project, I can't provide some real code snippets, hope you understand, although my test spec looks just like in the Grails doc example: extends from Specification, @Integration and Grails' @Rollback annotations are used on class level.

import grails.transaction.Rollback
import grails.test.mixin.integration.Integration

@Integration
@Rollback
class SomeSpec extends Specification {

@Shared
RESTClient client

def setup() {
    client = new RESTClient('http://localhost:8080')
}

I've tried everything: used @Rollback for each method separately, tried to use Spring's @Rollback in the same way, also example was wrong about it - it can't be applied to class, only to methods, I've tried to use @Transactional instead - no success.

Also I should mention that controller and services I'm using are annotated as Grails' @Transactional, although removing all the annotations didn't change anything.

I've googled for the whole day and wasn't able to find anything useful as those solutions were mostly for Grails 2, so I believe my question is unique around SO. Some of those solutions suggested using IntegrationSpec instead of Specification, reporting it is working like intended, but it was removed for Grails 3 and replaced with @Integration, so I believe that's not an option in my case.

I've also found numerous @Transaction solutions, but those didn't help either, although there's a small chance I did something wrong while trying to apply those solutions.

So, currently the only option is to try to make test as independent of data as possible and to drop and re-create database after each test suite, but this is very poor solution IMO. I don't believe no one faced the same problem as I did, so I hope to find some acceptable solution. I'll gladly provide any missing information I forgot to mention, of course.

UPDATE: I've tried the workaround mentioned here - I've used Transactional, as I've described before, and it still has no effect, changes are still committed, although I get this pair of messages for each test, meaning it is actually trying to do something. Not sure it will be helpful, but here it goes (it is edited a bit):

INFO org.springframework.test.context.transaction.TransactionContext - Began transaction (1) for test context [DefaultTestContext@3dbe8e11 testClass = SomeSpec, testInstance = package.SomeSpec@5e2296ae, testMethod = $spock_feature_0_0@SomeSpec, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@6ec335b0 testClass = SomeSpec, locations = '{}', classes = '{class package.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.IntegrationTest=true}', resourceBasePath = '', contextLoader = 'grails.boot.config.GrailsApplicationContextLoader', parent = [null]]]; transaction manager [org.grails.orm.hibernate.GrailsHibernateTransactionManager@25870a3a]; rollback [true] INFO org.springframework.test.context.transaction.TransactionContext - Rolled back transaction for test context [DefaultTestContext@3dbe8e11 testClass = SomeSpec, testInstance = package.SomeSpec@5e2296ae, testMethod = $spock_feature_0_0@SomeSpec, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@6ec335b0 testClass = SomeSpec, locations = '{}', classes = '{class package.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.IntegrationTest=true}', resourceBasePath = '', contextLoader = 'grails.boot.config.GrailsApplicationContextLoader', parent = [null]]].

回答1:

I had the same problem, but in my case, I noticed that everything I load to database with @LoadDataSet is correctly rolled back. But when I have any interaction with a column referenced by a foreign key, @rollback just don't delete the references and this is causing the error. I also tried so many @Transactional solutions, but still doesn't work.

My workaround is to call DomainClassName.executeUpdate('delete from DomainClassName') before the cleanup(), to all domainClasses I am expecting to change. The tests now work, not properly, sadly.

Update:

Check if this works for you.


Just to clarify: I solved my problem using a combination of @Transactional(propagation = Propagation.NESTED) in my test class and then, as the last step of each test, calling DomainClassName.executeUpdate('delete from DomainClassName') to every domain class I'm expecting to change.

My test class is now something like this:

@Integration
@Transactional(propagation = Propagation.NESTED)
@TestFor(MyService)
@LoadDataSet("PathInResources") //If needed
class MyTestSpec extends GebSpec {

  static transactional = true

  @Autowired
  SessionFactory session

  private void tearDownValues(){
    DomainClassName.executeUpdate('delete from DomainClassName') // Hibernate mapping will handle the table name for you
  }

  public void myTest() {
    //Test steps ...
    then:
    tearDownValues() //Call your tearDown method on the last step, after that and before cleanup(), auto rollback runs and you may have the same error as before
  }

Note that if you use @LoadDataSet you'll only need to call DomainClassName.executeUpdate('delete from DomainClassName') for the data you create inside your test. Spock will handle the rollback for everything @LoadDataSet creates.



回答2:

Annotate test class with

@WebIntegrationTest
@Transactional

Annotate method with

@Rollback

Operate on DataSource wrapped by

org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
.newInstance(dataSource)