We were migrating some existing Java code to Grails 2.0 and we were looking for a good solution to test domain constraints as we were migrating from an existing schema. We already use and love Spock for most of our testing needs. We came up with a relatively easy and reusable solution for testing constraints that I wanted to share. As we make improvements or changes I will update the post.
Update I have updated the project and samples to work with Grails 2.1.1 and Spock 0.7.
All files for this demonstration can be found at my grails-spock-constraints GitHub repository.
Setting Up A Domain Object
I set up an arbitrary Person object with some constraints defined on it that will help demonstrate how to test a wide variety of constraints using spock. The latest Person class can be found on github, but here the file at the time I published this post.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
As you can see there are variety of constraints defined for this object. Some of these might be nonsensical, but I wanted to demonstrate testing a wide variety of scenarios. To get the invalid.bountyhunter message working I simply added the following to the bottom of my message.properties file:
1
|
|
log in.
Grails Constraints
The following are a list of available constraints to define on your domain classes.
- attributes
- blank
- creditCard
- inList
- matches
- max
- maxSize
- min
- minSize
- notEqual
- nullable
- range
- scale
- size
- unique
- url
- validator
- widget
I will let the reader check the documentation for the specifics of each constraint, but I do want to mention a few Gotchas that I ran into:
Gottchas
- Setting an inList constraint will not throw an error when passing a blank ‘’ string. I had assumed that using inList: [‘one’,‘two’] would require the value to be not null and a valid value in list. Not True
- Setting a field as url type will allow blank and null values are valid urls unless you explicitly define blank or nullable as a constraint. (This seems to be fixed to not allow null by default in final 2.0.0)
- Setting column length for String type should be done using field: size 0..50 for a string that can be empty to 50 length. And would be set to field: size: 1..100 for a field that can not be empty and has a max length of 100. MaxSize and minSize are NOT valid for this.
I will add to that list as I run across items that are unexpected.
Spock Basics
If you aren’t familiar with Spock and it’s feature set, read up on it and start using it. It brings very rich and powerful tooling to testing your Grails application. The important pieces to be familiar with for these tests are the use of the where clause, parameterizations and the @Unroll features.
It is due to some Spock magic that these tests are able to do so much with such little code. You will see the line in the tests like the one below of:
1
|
|
Spock will replace both the field and val with data from the configured where table. I had to put the field in quotes and treat it as a gstring so Spock would replace that correctly as simply using the new Person(field: val) would correctly replace the val, but treat the field as an actual object property named field instead of being replaced with a valid field name from the where table.
Other Possible Techniques
We also really like the build test data plugin and started off writing these as integration tests with buildWithoutSave giving us fully hydrated live domain objects and simply changing the fields we wanted to invalidate. But with Grails’ ability to mock for constraints in unit tests we can run these tests even faster and earlier in the test cycle if we use unit tests instead. This is import to us as we can run the test-app -unit in a few seconds during development. Our machines are not the fastest and running -integration takes several tens of seconds to run.
Having the objects built with the build test data plugin is another good option that some people may opt for since you can mix a richer set of tests together since you are in a headless app during integration. I will leave that decision up the readers which method they prefer.
Testing Constraints
I first created a simple abstract helper class that can build some of our data for us with reusable methods. This class also holds the function that checks for the error message to exist on a field after the validate is called. It is called ConstraintUnitSpec.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
We then create our constraint tests and extend this class. We also use the new Grails 2.0 @TestFor annotation to inject some test helper methods such as mockForConstraintsTests since we are in the unit test phase.
1 2 3 4 |
|
We want to set up the test and tell Grails that we are mocking the person object so it will add the validate method and we can also add an existing person to test unique constraints against. We do this by adding the following.
1 2 3 4 5 6 7 |
|
After this is done we can start adding some test blocks. The first test I always add is the test for the standard constraint bounds. This will help with any refactoring that you you do later to change field definitions such as changing the size/length of a string. If you had size: 1..50 and your test checks for 0 and 51 and then change the field constraint to size: 0..50, this will cause your test to now fail and hopefully save you some headaches later so you can double check your new change against the domain model and data. Here is the comprehensive test for the Person class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Using Spock, these tests become very concise and easy to read in the where clause. I set it up so that the field when using val will cause the error constraint to be violated.
One of the reasons we like this method to test constraints is other constraint tests simply test for errors to exist on a field, but not the specific type of constraint violation. While the test for general field errors is still valid, it isn’t quite as fine grained as checking the actual type of constraint violation expected. An example of a generic constraint testing can be found in a couple places, but OPI published an article here on this style of constraint testing. This is a good starting point if you don’t need as fine grained control as I offer here.
As a side note, every time the test checks for a constraint violation there will be many fields violated, but we only care about and check for one specific field and constraint to be violated for each row in the where table as a time. It might be possible to mix multiple checks together, but we like testing each scenario and constraint individually.
Adding Valid Tests
We can also add additional tests that check for valid values. I added some logic in the validateConstraints method that will expect the field to pass validation if you use the value in the error column of ‘valid’ or just a null. Using the actual word ‘valid’ instead of null will help the test names be more concise when Spock unrolls them. In the following age tests we are checking for both failure and valid criteria. Since age is defined as:
1
|
|
We will be checking for values that fall at the limits, outside and inside the range as well as passing a null value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Here is the full PersonSpec
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
|
Conclusion
I hope that this can be a useful tool or guideline for you when you are creating constraint tests for your domain objects. These tests are a bit tedious to write, but using some of the techniques here makes writing them pretty quick to create and test.
I would probably never test ALL the constraints like this, but in case you wanted to test anything specific and guard against certain non-allowed data then you might want to consider something like this. This would certainly be to your benefit to do for any custom constraints you define on your objects.
Sample Project
All files for this demonstration can be found at my grails-spock- constraints GitHub repository.
Getting Grails 2.0 and Spock 0.6 Working (Legacy)
We had a little bit of trouble getting grails 2.0.0.RC1 and Spock 0.6 to play well together, but found some useful information in this jira. Add the following to the repositories section of your BuildConfig.groovy
1
|
|
and the following to the plugins section of your BuildConfig.groovy
1
|
|
These will no doubt change as 2.0 becomes final and the official spock plugin is updated.
References
- Another Spock Constraint Test: http://meetspock.appspot.com/script/35001
- Spock Documentation: http://code.google.com/p/spock/
- Generic Constraint Validation: http://www.objectpartners.com/2011/02/10/grails-testing-domain-constraints/
- Grails 2.0 Docs: http://grails.org/doc/2.0.x/
- Grails 2.0 Mocking: http://grails.org/doc/2.0.x/guide/testing.html#mockingCollaborators