Spockを使ってGrailsのドメインクラスのConstraintsを華麗にテストする

Spockのwikiでも
https://code.google.com/p/grails-spock-examples/wiki/Overview#Testing_constraints
というのが紹介されているのですが、もうちょっとカッチョいい方法が
http://www.christianoestreich.com/2011/11/domain-constraints-grails-spock/
で紹介れていたのでやってみます。

まずSpockの準備
BuildConfig.groovyにspockを追加

    plugins {
        ...
        test ":spock:0.6"
        ...
    }

先のURLではConstraintUnitSpecという形で、Specificationを継承したクラスを作っているのですが、少し趣向を変えてPOJOで書いてみます。
場所は適当なところに。

class ConstraintsUnitTestMixin {

    void validate(obj, field, error) {
        assert error
        def validated = obj.validate()
        if (error == 'valid') {
            assert !obj.errors[field]
        } else {
            assert !validated
            assert obj.errors[field]
            assert error == obj.errors[field]
        }
    }
}

準備はこれで完了。ではではこんなドメインがあったとします。

class Person {
    String username
    Integer age
    static constraints = {
        username nullable: false, size: 4..20, unique: true
        age nullable: true, range: 0..200
    }
}

でconstraintsのテストはこんな感じになります。

import grails.plugin.spock.UnitSpec
import grails.test.mixin.TestFor
import grails.test.mixin.TestMixin
import org.apache.commons.lang.RandomStringUtils
import spock.lang.Unroll

@TestFor(Person)
@TestMixin([ConstraintsUnitTestMixin])
class PersonSpec extends UnitSpec {

    def setup() {
        mockForConstraintsTests(Person, [new Person(username: "yamkazu")])
    }

    @Unroll
    def "personの#fieldに#valを設定すると#errorとなる"() {
        when:
        def obj = new Person("$field": val)

        then:
        validate(obj, field, error)

        where:
        error          | field      | val
        'nullable'     | 'username' | null
        'size'         | 'username' | "abc"
        'valid'        | 'username' | "abcd"
        'valid'        | 'username' | RandomStringUtils.randomAlphabetic(20)
        'size'         | 'username' | RandomStringUtils.randomAlphabetic(21)
        'unique'       | 'username' | "yamkazu"
        'valid'        | 'age'      | null
        'range'        | 'age'      | -1
        'valid'        | 'age'      | 0
        'valid'        | 'age'      | 100
        'valid'        | 'age'      | 200
        'range'        | 'age'      | 201
    }

}

ちょっとだけ解説。
@TestMixinを使って継承しないでvalidateを読み込んでみました。今のところIDEの補完が効かなくなるのが難点ですが、、どうでしょうか。
@Unrollを使うとwhereで食わせる各パラメータがそれぞれ独立したテストとして扱われるみたいです。あと紹介したURLでは

@Unroll({"test person all constraints $field is $error"})

という形でクロージャを使っているのですが、このままだとうまく動きませんでした。今のところ現在の最新で試すと

@Unroll("test person all constraints #field is #error")

とするとうまくいきます。この記事の例でやったように単に@Unrollだけつけてメソッド名に文字列を指定してもOK。

ちなみに結果はこんなん。
f:id:yamkazu:20120519223859p:image

カッチョイイ!