Reputation: 567
This is a simple domain class in a grails application:
class User {
String username
static constraints = {
username unique: true
}
}
My question is: should I write the unit test to check if a username field is unique?
@Test
void cannotCreateMoreThanOneUserWithTheSameUsername() {
new User(username: 'john').save()
def secondUser = new User(username: 'john')
assert !secondUser.validate()
}
I'm in doubt because:
If I write the User class in accordance with TDD principles then I should write failing test before implementing constraints closure.
On the other hand, setting an unique constraint in domain is rather a data model configuration than a true logic. And what's more, the save and validate methods are implemented in the framework.
Upvotes: 2
Views: 209
Reputation: 567
After some more research I want to share a next test sample for the same User class and finally answer my own question.
@Test
void usernameIsUnique() {
def mock = new ConstraintsMock()
User.constraints.delegate = mock
User.constraints.call()
assert mock.recordedUsernameUniqueConstraint == true
}
class ConstraintsMock {
Boolean recordedUsernameUniqueConstraint = null
def username = { Map m ->
recordedUsernameUniqueConstraint = m.unique
assert m.unique
}
}
This is very naive test sample. It is rather a test of implementation than a behaviour, what in my opinion is bad. But does it really differ from a test sample in the question?
First things first: what logic do we want to test? What is the the true logic of constraints closure? It's just calling a gorm's dynamic methods for each field we want to configure and passing a configuration as a parameter. So why not just call this closure in test? Why would I call save method? Why would I call gorm's validate method? From this point of view, directly calling constraints closure in unit tests seems to be not so bad idea.
On the other hand, what is a difference between constraints closure and configuration closures in Config.groovy? We do not test a configuration, do we? I think we don't test a configuration because the test of configuration would be just like a carbon copy of this configuration (repeating ourselves). What's more, this kind of test won't even increase code coverage, if somebody still cares about this metrics today, because first run of integration or functional test should run all constraints of all domains.
The last thing: what kind of bug this test is able to catch in a real life?
To summarize: in my opinion setting up the simple constraints like "blank", "nullable" or unique is very similar to the application configuration. We should not test this part of code because if a such test is something more than carbon copy of our constraints definition it propably checks only a framework's logic.
I wrote many unit tests for constraints. Now I'm removing them back during a refactoring. I will leave only unit tests of my own validators logic.
Upvotes: 0
Reputation: 24291
I would concentrate your testing effort on areas that are likely to go wrong, rather than trying to obtain 100% coverage.
With this in mind, I avoid testing anything that is simply declared. There is no logic for you to break and any test would just be repeating the declaration. It's hard to see how this would save you from accidently breaking this functionality.
If you were coding the underlying library that deals with the declaration, then you should be writnig tests. If not, rely on the library. Of course, if you didn't trust the library authors to get this right, then you could write tests. There is a trade-off here of testing effort vs reward.
Upvotes: 1
Reputation: 35864
In my opinion, unit testing CRUD methods is not worth the time since the Grails devs have fully tested these already. Unit testing constraints, on the other hand, is important because constraints can change during the lifecycle of your app and you want to make sure you're catching those changes. You never know what business logic might need to be modified to support said changes. I like to use Spock for this and a typical constraint test would look something like this:
@TestFor(User)
class UserSpec extends ConstraintUnitSpec {
def setup() {
mockForConstraintsTests(User, [new User(username: 'username', emailAddress: '[email protected]')])
}
@Unroll("test user all constraints #field is #error")
def "test user all constraints"() {
when:
def obj = new User("$field": val)
then:
validateConstraints(obj, field, error)
where:
error | field | val
'blank' | 'username' | ' '
'nullable' | 'username' | null
'unique' | 'username' | 'username'
'blank' | 'password' | ' '
'nullable' | 'password' | null
'maxSize' | 'password' | getLongString(65)
'email' | 'emailAddress' | getEmail(false)
'unique' | 'emailAddress' | '[email protected]'
'blank' | 'firstName' | ' '
'nullable' | 'firstName' | null
'maxSize' | 'firstName' | getLongString(51)
'blank' | 'lastName' | ' '
'nullable' | 'lastName' | null
'maxSize' | 'lastName' | getLongString(151)
'nullable' | 'certificationStatus' | null
}
}
Here's the ConstraintUnitSpec base class:
abstract class ConstraintUnitSpec extends Specification {
String getLongString(Integer length) {
'a' * length
}
String getEmail(Boolean valid) {
valid ? "[email protected]" : "test@w"
}
String getUrl(Boolean valid) {
valid ? "http://www.google.com" : "http:/ww.helloworld.com"
}
String getCreditCard(Boolean valid) {
valid ? "4111111111111111" : "41014"
}
void validateConstraints(obj, field, error) {
def validated = obj.validate()
if (error && error != 'valid') {
assert !validated
assert obj.errors[field]
assert error == obj.errors[field]
} else {
assert !obj.errors[field]
}
}
}
This is a technique I learned from a blog post. But I can't recall it right now. I'll hunt for it and if I find it, I'll be sure and link to it.
Upvotes: 6