promanski
promanski

Reputation: 567

Grails domain standard validators: should I test or not?

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:

Upvotes: 2

Views: 209

Answers (3)

promanski
promanski

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

WW.
WW.

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

Gregg
Gregg

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

Related Questions