Grails におけるドメインクラスのユニットテスト - Grails標準テストと Easyb プラグイン使用

Grails におけるドメインクラスのユニットテストに関して、Grails 標準テストと Easyb(BDD 用のツール) プラグインを使った 2通りを紹介する。

ドメインクラスのユニットテストを行ううえでのポイントは、実行時にドメインクラスに適用される save(), validate() などのメソッドがそのままでは使えないという事。

これを解決するために GrailsUnitTestCase には以下のようなメソッドが用意されている。(他にも mockFor, mockLogging, mockController, mockTagLib などがある)

  • mockDomain : ドメインクラスにモックメソッドを用意(save や validate メソッドが使えるようになる)
  • mockForConstraintsTests : 制約条件のみチェックする mockDomain の簡易版(validate メソッドが使えるようになる)

これらのメソッドは、ドメインクラスにモックメソッドを追加してくれるので、ユニットテストで制約条件のチェック等が実施できるようになる。

なお、Easyb プラグインでも上記のメソッドが使用できるので、Easyb 側の特殊な対応は不要となっている。

Easyb プラグインのインストール

まず、Grails で Easyb が実行できるように Easyb 用のプラグインをインストールする。現時点でプラグインは以下の 2種類が用意されているが、今回は easyb-1.1 の方を使用する。

インストールの仕方は以下の通り。

easyb プラグインのインストール(grails-easyb-1.1 の方)
>grails install-plugin easyb

ユニットテスト対象のドメインクラス

テスト対象のドメインクラスの内容は以下。name に対して null とブランクを許可しない制約を定義。

grails-app/domain/sample/User.groovy
package sample

class User {
    String name

    static constraints = {
        name(nullable: false, blank: false)
    }
}

Grails標準テストの場合

まずは、一般的な Grailsユニットテストを定義して実行する。User クラスを使用する前に mockForConstraintsTests や mockDomain で User クラスに対するモックメソッドの適用を実施。

test/unit/sample/UserTests.groovy
package sample

import grails.test.*

class UserTests extends GrailsUnitTestCase {
    protected void setUp() {
        super.setUp()
    }

    protected void tearDown() {
        super.tearDown()
    }

    //制約のテスト
    void testConstraints() {
        mockForConstraintsTests(User)
        //mockForConstraintsTests の代わりに以下でも可
        //mockDomain(User)

        def user = new User()
        assertFalse user.validate()
        assertEquals "nullable", user.errors["name"]

        user.name = ""
        assertFalse user.validate()
        assertEquals "blank", user.errors["name"]

        user.name = "テスター"
        assertTrue user.validate()
    }

    //永続化のテスト
    void testPersistence() {
        mockDomain(User)

        assertEquals 0, User.count()

        def user = new User(name: "テスター")
        user.save()

        assertEquals 1, User.count()

        assertEquals "テスター", User.list()[0].name
    }
}
実行
>grails test-app
・・・
-------------------------------------------------------
Running 2 unit tests...
Running test sample.UserTests...PASSED
Tests Completed in 1750ms ...
-------------------------------------------------------
Tests passed: 2
Tests failed: 0
-------------------------------------------------------

Easyb の場合

次に、Easyb のストーリー形式でスペックを定義して実行する。内容的には上記のテストと同じ。

test/unit/sample/UserStory.groovy
package sample

scenario "User の名前制約", {
    given "User オブジェクト", {
        mockForConstraintsTests(User)
        //mockForConstraintsTests の代わりに以下でも可
        //mockDomain(User)

        user1 = new User()
        user2 = new User()
        user3 = new User()
    }
    when "名前を設定", {
        user2.name = ""
        user3.name = "テスター"
    }
    then "null は許可しない", {
        user1.validate().shouldBe false
        user1.errors["name"].shouldBe "nullable"
    }
    and "ブランクは許可しない", {
        user2.validate().shouldBe false
        user2.errors["name"].shouldBe "blank"
    }
    and "'テスター' は許可", {
        user3.validate().shouldBe true
    }
}

scenario "User の永続化", {
    given "'テスター' User オブジェクト", {
        mockDomain(User)
        user = new User(name: "テスター")
    }
    when "保存", {
        saveUser = {
            user.save()
        }
    }
    then "保存されている", {
        User.count().shouldBe 0

        //save メソッドの実行
        saveUser()

        User.count().shouldBe 1

        User.list()[0].name.shouldBe "テスター"
    }
}
実行
>grails easyb-test
・・・
-------------------------------------------------------
Running 1 unit test of framework easyb...
before testHelper.runTests()
after testHelper.runTests()
Tests Completed in 9516ms ...
-------------------------------------------------------
Tests passed: 2
Tests failed: 0
-------------------------------------------------------