Loading...

April 13, 2009

Unit testing constraints in domain object in Grails

We can unit test constraints in domain objects with the mockForConstraintsTests(domainClass) method. This will add a validate() method to our domain class. Normally the validate() is dynamically added to our domain class when we run our Grails application. But with the mockForConstraintsTests(domainClass) method we can use it without running a complete Grails application.

Suppose we have the following domain class with a lot of constraints defined for the different properties:

package com.mrhaki.grails.unittest

class User {

    static constraints = {
        name       blank: false, maxSize: 40, notEqual: 'testEquals'
        nickName   blank: false, unique: true, minSize: 4, maxSize: 20,
                   validator: {
                       if (it == 'groovy') {
                           ['invalid.groovyness']
                       }
                   }
        avatar     nullable: true, validator: {val, obj ->
                       if (val != null) {
                           val != obj.name
                       }
                   }
        creditCard nullable: true, creditCard: true
        email      nullable: true, email: true, matches: /.*\.nl$/
        type       nullable: false, inList: ['Admin', 'User']
        points     nullable: true, max: 1000, min: 100
        age        nullable: true, range: 12..120
        blogURL    nullable: true, url: true
        extra      nullable: true, size: 4..25
    }

    String name
    String nickName
    String avatar
    String creditCard
    String email
    String type
    Integer points
    Integer age
    String blogURL
    String extra
}

To unit test the constraints we can simply write a unit test class which extends from GrailsUnitTestCase. Then we must invoke mockForConstraintsTests(User) so the validate() method is added to our user class. Next we can set values for the properties and test if the validation succeeds or fails. We run the tests with the following grails command:

grails test-app -unit

Here is the code for the unit test class:

package com.mrhaki.grails.unittest

import grails.test.*

class UserTests extends GrailsUnitTestCase {

    def user

    protected void setUp() {
        super.setUp()
        // Make sure we can invoke validate() on our User domain object.
        mockForConstraintsTests(User)
        // Set up default user, so we can easily test single properties.
        user = new User(name: 'test', nickName: 'tester', type: 'User')
    }

    /**
     * class User{
     *     ...
     *     nickName unique: true
     *     ...
     * }
     */
    void testUnique() {
        // Test user to test uniqueness of nickName property.
        def test = new User(name: 'tester', nickName: 'tester', type: 'User')
        mockForConstraintsTests(User, [test])

        assertFalse user.validate()
        assertEquals 'Nickname is not unique.', 'unique', user.errors['nickName']

        user = new User(name: 'test', nickName: 'otherTester', type: 'User')
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     name notEqual: 'testEquals'
     *     ...
     * }
     */
    void testNotEqual() {
        user.name = 'testEquals'
        assertFalse user.validate()
        assertEquals 'Name is equal to testEquals.', 'notEqual', user.errors['name']

        user.name = 'test'
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     name blank: false
     *     nickName blank: false
     *     ...
     * }
     */
    void testBlank() {
        user = new User(name: '', nickName: '', type: 'Admin')
        assertFalse user.validate()
        assertEquals 'Name is blank.', 'blank', user.errors['name']
        assertEquals 'NickName is blank.', 'blank', user.errors['nickName']

        user = new User(name: 'test', nickName: 'tester', type: 'User')
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     avatar nullable: true
     *     creditCard nullable: true
     *     email nullable: true
     *     type nullable: false
     *     points nullable: true
     *     age nullable: true
     *     blogURL nullable: true
     *     extra nullable: true
     *     ...
     * }
     */
    void testNullable() {
        user = new User()
        assertFalse user.validate()
        assertEquals 'Name is null.', 'nullable', user.errors['name']
        assertEquals 'NickName is null', 'nullable', user.errors['nickName']
        assertNull user.errors['avatar']
        assertNull user.errors['creditcard']
        assertNull user.errors['email']
        assertEquals 'Type is null.', 'nullable', user.errors['type']
        assertNull user.errors['points']
        assertNull user.errors['age']
        assertNull user.errors['blogURL']
        assertNull user.errors['extra']
    }

    /**
     * class User {
     *     ...
     *     nickName minSize: 4
     *     ...
     * }
     */
    void testMinSize() {
        user = new User(name: 'test', nickName: 'tst', type: 'User')
        assertFalse user.validate();
        assertEquals 'NickName is not minSize 4.', 'minSize', user.errors['nickName']

        user.nickName = 'tester'
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     name maxSize: 40
     *     nickName maxSize: 20
     *     ...
     * }
     */
    void testMaxSize() {
        user.name = 'This name is just way longer than the maxSize argument defines.'
        user.nickName = 'The nickName is just too long, people will not remember this.'
        assertFalse user.validate()
        assertEquals 'Name is too long.', 'maxSize', user.errors['name']
        assertEquals 'NickName is too long.', 'maxSize', user.errors['nickName']

        user.name = 'Hubert Klein Ikkink'
        user.nickName = 'mrhaki'
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     email email: true
     *     ...
     * }
     */
    void testEmail() {
        user.email = 'test'
        assertFalse user.validate()
        assertEquals 'Not a valid email.', 'email', user.errors['email']

        user.email = 'valid@country.nl'
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     email matches: /.*\.nl$/
     *     ...
     * }
     */
    void testMatches() {
        user.email = 'test@country.com'
        assertFalse user.validate()
        assertEquals "Doesn't end with .nl.", 'matches', user.errors['email']

        user.email = 'test@country.nl'
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     creditCard creditCard: true
     *     ...
     * }
     */
    void testCreditCard() {
        user.creditCard = '1234512'
        assertFalse user.validate()
        assertEquals 'Not a valid creditcard#.', 'creditCard', user.errors['creditCard']
        
        user.creditCard = '4417123456789113'
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     type inList: ['Admin', 'User']
     *     ...
     * }
     */
    void testInList() {
        user.type = 'NotValid'
        assertFalse user.validate()
        assertEquals 'NotValid not in list for type.', 'inList', user.errors['type']

        user.type = 'user'
        assertFalse user.validate()
        assertEquals 'inType validator is case sensitive.', 'inList', user.errors['type']

        user.type = 'User'
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     points max: 1000
     *     ...
     * }
     */
    void testMax() {
        user.points = 20000
        assertFalse user.validate()
        assertEquals '20000 is above max for points.', 'max', user.errors['points']

        user.points = 900
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     points min: 100
     *     ...
     * }
     */
    void testMin() {
        user.points = 10
        assertFalse user.validate()
        assertEquals '10 is below min for points.', 'min', user.errors['points']

        user.points = 210
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     extra size: 4..25
     *     ...
     * }
     */
    void testSize() {
        user.extra = 'ab'
        assertFalse user.validate()
        assertEquals "'ab' size is below 4 for extra.", 'size', user.errors['extra']

        user.extra = 'This text is too long to be of the correct size.'
        assertFalse user.validate()
        assertEquals 'Text is longer than 25 for extra.', 'size', user.errors['extra']

        user.extra = 'This is the right length.'
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     age range: 12..120
     *     ...
     * }
     */
    void testRange() {
        user.age = 0
        assertFalse user.validate()
        assertEquals '0 is below range for age.', 'range', user.errors['age']

        user.age = 200
        assertFalse user.validate()
        assertEquals '200 is above range for age.', 'range', user.errors['age']

        user.age = 40
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     blogURL url: true
     *     ...
     * }
     */
    void testURL() {
        user.blogURL = 'invalid-url'
        assertFalse user.validate()
        assertEquals "'invalid-url' is not valid.", 'url', user.errors['blogURL']

        user.blogURL = 'http://www.mrhaki.com'
        assertTrue user.validate()
    }

    /**
     * class User {
     *     ...
     *     nickName validator: {
     *                 if (it == 'groovy') {
     *                     ['invalid.groovyness']
     *                 }
     *              }
     *     avatar validator: {val, obj ->
     *                 if (val != null) {
     *                     val != obj.name
     *                 }
     *            }
     *     ...
     * }
     */
    void testCustomValidator() {
        user.avatar = 'test'
        assertFalse user.validate()
        assertEquals "'test' is the same as name.", 'validator', user.errors['avatar']

        user.avatar = 'avatar-test'
        assertTrue user.validate()

        // Test custom error key.
        user.nickName = 'groovy'
        assertFalse user.validate()
        assertEquals "'groovy' not allowed as nickName.", 'invalid.groovyness', user.errors['nickName']

        user.nickName = 'tester'
        assertTrue user.validate()
    }
}