Skip to content

TestDataConfig

Aaron edited this page Oct 27, 2015 · 9 revisions

If you are using Grails 2.x, you can create a sample configuration file is created at grails-app/conf/TestDataConfig.groovy by running:

grails install-build-test-data-config-template

For Grails 3.x, you will need to manually create a TestDataConfig.groovy (using the examples below as a starting point):

src/test/resources/TestDataConfig.groovy

The config file is most useful when you are building a class that has dependencies on an entire object graph and you want to be able to control some of the values in the object graph that gets created automatically.

For example, if you're building a Book, and a Book has an Author which has a Publisher. If you want to control the name of the publisher (without manually creating the entire object graph by hand), you can instead specify in the config file that when publishers are created, they should be named X:

    testDataConfig {
        sampleData {
            Publisher {
                name = "Pragmatic Bookshelf"  // default Publisher name unless manually overridden
            }
        }
    }

Then, when you build a Book, the Publisher is named appropriately:

    assert "Pragmatic Bookshelf" == Book.build().author.publisher.name

Inside the config's sampleData closure, you specify a domain class name. If the class is in a package (as is good practice for domain classes), just use quotes around the fully qualified class name.

For each class specified, you can then provide the properties that you'd like populated and give it a property value to use:

    testDataConfig {
        sampleData {
            'com.example.Hotel' {
                name = "Hilton"
            }
        }
    }

Using that config file in a test for a com.example.Hotel domain object with a name property would produce the following results in a test:

    println Hotel.build().name   // prints "Hilton"

It follows the normal groovy config file pattern and you can also override things at an environmental level if desired. This example shows how in development we can return "Motel 6" but production will return "Hilton".

    testDataConfig {
        sampleData {
                'com.example.Hotel' {
                    name ="Motel 6"
                }
        }
    }
    environments {
        production {
            testDataConfig {
                sampleData {
                    'com.example.Hotel' {
                        name = "Hilton"
                    }
                }
            }
        }
    }

Generating Dynamic Values

If you have a need to have different values given to a domain object (possibly for a unique constraint), you can instead specify a closure in your config file. This closure will be called and is expected to return the property value. Here's an example that generates a unique title for a book:

    testDataConfig {
        sampleData {
            'com.foo.Book' {
                def i = 1
                title = {-> "title${i++}" }   // creates "title1", "title2", ...."titleN"
            }
        }
    }

One thing to watch out for with this is that if your tests are expecting particular values generated by the closure, you could run into sequencing effects if your test run out of order (or you add a new test). To get around this, you can reset the config before you run the test. This is a good idea if you're using dynamic values:

void setUp() {
    grails.buildtestdata.TestDataConfigurationHolder.reset()
}

Test Specific Config

It's also possible to specify a config that is used during a particular test. Just set the sampleData property on the TestDataConfigurationHolder to a map containing your configuration values, where the key in the map is the Domain class (with package) and the value is another map where the keys are property names and the values are the value to assign to the property.

Using a static value:

    def hotelNameAlwaysHilton = [Hotel: [name: "Hilton"]]
    TestDataConfigurationHolder.sampleData = hotelNameAlwaysHilton

    def hilton = Hotel.build()
    assertEquals "Hilton", hilton.name

Using a closure for dynamic values:

    def i = 0
    def hotelNameAlternates = [
            ('config.Hotel'): [name: {->
                i++ % 2 == 0 ? "Holiday Inn" : "Hilton"
            }]
    ]
    TestDataConfigurationHolder.sampleData = hotelNameAlternates

    def holidayInn = Hotel.build()
    assertEquals "Holiday Inn", holidayInn.name

    def hilton = Hotel.build()
    assertEquals "Hilton", hilton.name

    def backToHolidayInn = Hotel.build()
    assertEquals "Holiday Inn", backToHolidayInn.name

After using this method, you'll likely want to call TestDataConfigurationHolder.reset() to put the configuration back to the normal config file.

    void tearDown() {
        grails.buildtestdata.TestDataConfigurationHolder.reset()
    }

Specifying transitive dependencies (unit tests only)

For performance reasons, Grails will not Mock out your entire object model for a unit test. It will only Mock those domain classes that you explicitly specify with the @Mock annotation. Because the build-test-data @Build annotation relies on the default @Mock behavior in unit tests, it needs a little help to know which domain classes depend on other domain classes.

Occasionally it is necessary to build another object manually in your TestDataConfig file. Usually this will look something like this:

    'test.Book' {
        author = {-> Author.build() }
    }

There are a number of reasons that you might want to do this. For complex object graphs, this may be necessary to prevent failures due to loops in the graph. Also, for some graphs, you may need to do a buildWithoutSave() or a buildLazy() for a particular association. It may also just be that you want to default an optional association, since build-test-data will only build out the graph for required properties.

Regardless, this introduces a hidden dependency when building the Book class. If you only have @Build([Book]) in your test case, you'll likely get a MethodMissing exception building Author. To resolve this issue, you may specify additional objects to mock each time Book is mocked:

    unitAdditionalBuild = ['test.Book': [test.Author]]

Each key is the full package name of a domain object and the value is a List of either a Class or String indicating additional domains to include whenever the key object is built.

The chain is recursive in that Author could, itself, have additional dependencies. So if Book belongsTo an Author which belongsTo a Publisher, building a Book would give you all three without explicitly saying that a Book needs a Publisher with this:

    unitAdditionalBuild = ['test.Book': [test.Author], 'test.Author': [test.Publisher]]

Again, this is only needed for Unit testing as Grails includes all domain objects, by default, for integration tests.

Default subclass when building abstract domain objects

When build-test-data encounters a reference to an abstract class it will attempt to find a concrete subclass in order to satisfy the build. This will happen anytime you have a polymorphic association that is required in your object graph.

By default, build-test-data will find all concrete subclasses, sort them alphabetically by class name, and return the first one in the list.

By using the abstractDefault option, you can override this behavior globally to indicate which specific subclass is desired for a given base class.

    abstractDefault = ['test.AbstractBook': MyBook, 'test.AbstractAuthor': Tolkien]

In this example, any reference to AbstractBook in a domain class will build an instance of MyBook by default. If you only want to override the subclass for a particular domain object, you may want to consider just adding a default value in sampleData (see the section on transitive dependencies for more information).

This default will also apply if you manually build an instance of an abstract class. AbstractBook.build() in this example will return a MyBook instance.

Turning off the build-test-data plugin in some environments

The build-test-data plugin should be safe and have no real impact on environments that it's build methods aren't used in. If you'd still like to disable it (to gain a small startup performance benefit by not decorating the metaClass), you can set the enabled flag to false.

    environments {
        production {
            testDataConfig {
                enabled = false
            }
        }
    }

By default, all environments are enabled so this flag is only required if you want to disable it.

Using a different name for TestDataConfig.groovy

In some cases you need to have a different name for TestDataConfig.groovy. The reason could be, that you have project that depends on several inline plugins all using this plugin. If all plugins contains TestDataConfig.groovy you will end up with a compile error saying that TestDataConfig.groovy is duplicated in your classpath.

Using a different name by setting this in Config.groovy (or application.groovy for Grails 3):

    grails.buildtestdata.testDataConfig = "MyTestDataConfig"

With Grails 3, you would add to application.yml:

    grails:
        buildtestdata:
            testDataConfig: "MyTestDataConfig"

and rename your TestDataConfig.groovy to MyTestDataConfig.groovy