-
Notifications
You must be signed in to change notification settings - Fork 41
TestDataConfig
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"
}
}
}
}
}
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()
}
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()
}
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
, build
ing 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.
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.
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.
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