Kimble
Kimble

Reputation: 7574

Grails - Recreate database schema for integration test

Is there a convenient way to force Grails / Hibernate to recreate the database schema from an integration test?

Upvotes: 2

Views: 3394

Answers (3)

Kimble
Kimble

Reputation: 7574

This seems to work fine, but it's obviously very tightly coupled to H2 so it would have been nice if the Hibernate plugin had exposed an api to take care of this.

http://h2database.com/html/grammar.html#script

class SomethingTestingTransactionsSpec extends IntegrationSpec {

    static transactional = false // Why I need this

    SessionFactory sessionFactory // Injected by Spring
    DataSource dataSource // Also injected

    File schemaDump
    Sql sql    


    void setup() {
        sql = new Sql(dataSource)
        schemaDump = File.createTempFile("test-database-dump", ".sql") // Java 7 API
        sql.execute("script drop to ${schemaDump.absolutePath}")
    }

    void cleanup() {
        sql.execute("runscript from ${schemaDump.absolutePath}")
        sessionFactory.currentSession.clear()
        schemaDump.delete()
    }

    // Spock tests ...

}

It should be trivial to extract this code into a bean registered only for test environments. That should clean up the test code a bit and improve efficiency by only having to dump the schema once.

Upvotes: 1

Tomas Lin
Tomas Lin

Reputation: 3532

Well, you have access to executing arbitrary sql via sessionFactory, so you could call a grails schema export at the beginning of your tests and then just re-import the schema into your DB when needed.

Alternatively, I wonder if calling database migration plugin externally will accomplish the same.

Or you can trick grails into thinking your domain class has changed and force a reload via https://github.com/grails/grails-core/blob/v2.1.1/grails-hibernate/src/main/groovy/org/codehaus/groovy/grails/plugins/orm/hibernate/HibernatePluginSupport.groovy#L340 ( don't ask me how )

Upvotes: 0

Dónal
Dónal

Reputation: 187379

If you add the following in DataSource.groovy an empty database will be created before the integration tests are run:

environments {
    test {
        dataSource {
            dbCreate = "create"
        }
    }
}

By default each integration test executes within a transaction that is rolled-back at the end of the test, so unless you're not using this default behaviour there shouldn't be any need to programatically recreate the database.

Update

Based on your comment, it seems you really do want to recreate the schema before some integration tests. In that case, the only way I can think of, is to run


class MyIntegrationTest {

    SessionFactory sessionFactory

    /**
     * Helper for executing SQL statements
     * @param jdbcWork A closure that is passed an <tt>Sql</tt> object that is used to execute the JDBC statements
     */
    private doJdbcWork(Closure jdbcWork) {

        sessionFactory.currentSession.doWork(

            new Work() {
                @Override
                void execute(Connection connection) throws SQLException {

                    // do not close this Sql instance ourselves
                    Sql sql = new Sql(connection)
                    jdbcWork(sql)
                }
            }
        )
    }

    private recreateSchema() {

        doJdbcWork {Sql sql ->
           // use the sql object to drop the database and create a new blank database
           // something like the following might work for MySQL
           sql.execute("drop database my-schema")
           sql.execute("create database my-schema")
        }

        // generate the DDL and import it
        // there must be a better way to execute a grails command from within an
        // integration test, but unfortunately I don't know what it is            
        'grails test schema-export export'.execute()
    }

    @Test 
    void myTestMethod() {
        recreateSchema() 
        // now do the test
    }
}

First and foremost, this code is completely untested, so treat with deep suspicion and low expectations. Secondly, you may need to change the default transational behaviour of integration tests (with @Transactional) in order for this to work.

Upvotes: 1

Related Questions