Grails 2.2.4:瞬态属性:为什么自定义验证器被调用两次?

时间:2016-02-03 22:31:39

标签: spock grails-2.2 grails-validation grails-constraints

给定一个具有瞬态属性的简单域,例如:

package org.example.domain

class Ninja {

    String name
    String sensei

    static transients = ['name']

    static constraints = {
        name nullable:false, bindable:true, validator: { val, obj, errors ->
            obj.log.error "[VALIDATING] 'name': $val, $obj FIRED!", new Exception()
        }
        sensei nullable:false, bindable:true, validator:{ val, obj, errors ->
            obj.log.error "[VALIDATING] 'sensei': $val, $obj FIRED!", new Exception()
        }
    }
}

一个简单的单元测试域的约束,如下所示:

package org.example.domain

import grails.test.mixin.Mock
import spock.lang.Specification
import grails.test.mixin.TestFor

@TestFor(Ninja)
class NinjaSpec extends Specification {

    def "Should not fire the name's custom validator twice"() {
        given:
            def instance = new Ninja(name: 'Naruto',
                                   sensei: 'Kakashi')
        when:
            instance.validate(['sensei'])
        then:
            instance.errors['name'] == null
    }
}

当我运行此规范时,这只是为了验证非瞬态属性 sensei ,会发生两件意想不到的事情:

  1. 正在调用两个自定义验证程序;
  2. 不仅要调用名称的自定义验证程序,而且实际上它被称为两次
  3. 检查出来:

    ➜  grails test-app unit:spock -echoOut NinjaSpec
    
    | Running 1 unit test... 1 of 1
    
    --Output from Should not fire the name's custom validator twice--
    
    2016-02-04 19:08:25.208 [main] grails.app.domain.org.example.domain.Ninja
     ERROR [VALIDATING] 'name': Naruto, org.example.domain.Ninja : (unsaved) FIRED!
    
    java.lang.Exception
        at org.example.domain.Ninja$__clinit__closure1_closure2.doCall(Ninja.groovy:15)
        at org.grails.datastore.gorm.GormValidationApi.doValidate(GormValidationApi.groovy:64)
        at org.grails.datastore.gorm.GormValidationApi.validate(GormValidationApi.groovy:107)
        at org.example.domain.NinjaSpec.$spock_feature_0_0(NinjaSpec.groovy:17)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.invokeFeatureMethod(BaseSpecRunner.java:285)
        at org.spockframework.runtime.BaseSpecRunner.doRunIteration(BaseSpecRunner.java:256)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.runIteration(BaseSpecRunner.java:223)
        at org.spockframework.runtime.BaseSpecRunner.initializeAndRunIteration(BaseSpecRunner.java:214)
        at org.spockframework.runtime.BaseSpecRunner.runSimpleFeature(BaseSpecRunner.java:205)
        at org.spockframework.runtime.BaseSpecRunner.doRunFeature(BaseSpecRunner.java:199)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.runFeature(BaseSpecRunner.java:175)
        at org.spockframework.runtime.BaseSpecRunner.runFeatures(BaseSpecRunner.java:152)
        at org.spockframework.runtime.BaseSpecRunner.doRunSpec(BaseSpecRunner.java:112)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.runSpec(BaseSpecRunner.java:91)
        at org.spockframework.runtime.BaseSpecRunner.run(BaseSpecRunner.java:82)
        at org.spockframework.runtime.Sputnik.run(Sputnik.java:63)
        at org.junit.runners.Suite.runChild(Suite.java:128)
        at org.junit.runners.Suite.runChild(Suite.java:24)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
        at grails.plugin.spock.test.GrailsSpecTestType.doRun(GrailsSpecTestType.groovy:73)
        at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:290)
        at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:248)
        at _GrailsTest_groovy$_run_closure1_closure21.doCall(_GrailsTest_groovy:195)
        at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:184)
        at TestApp$_run_closure1.doCall(TestApp:82)
    
    2016-02-04 19:08:25.216 [main] grails.app.domain.org.example.domain.Ninja
     ERROR [VALIDATING] 'sensei': Kakashi, org.example.domain.Ninja : (unsaved) FIRED!
    
    java.lang.Exception
        at org.example.domain.Ninja$__clinit__closure1_closure3.doCall(Ninja.groovy:18)
        at org.grails.datastore.gorm.GormValidationApi.doValidate(GormValidationApi.groovy:64)
        at org.grails.datastore.gorm.GormValidationApi.validate(GormValidationApi.groovy:107)
        at org.example.domain.NinjaSpec.$spock_feature_0_0(NinjaSpec.groovy:17)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.invokeFeatureMethod(BaseSpecRunner.java:285)
        at org.spockframework.runtime.BaseSpecRunner.doRunIteration(BaseSpecRunner.java:256)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.runIteration(BaseSpecRunner.java:223)
        at org.spockframework.runtime.BaseSpecRunner.initializeAndRunIteration(BaseSpecRunner.java:214)
        at org.spockframework.runtime.BaseSpecRunner.runSimpleFeature(BaseSpecRunner.java:205)
        at org.spockframework.runtime.BaseSpecRunner.doRunFeature(BaseSpecRunner.java:199)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.runFeature(BaseSpecRunner.java:175)
        at org.spockframework.runtime.BaseSpecRunner.runFeatures(BaseSpecRunner.java:152)
        at org.spockframework.runtime.BaseSpecRunner.doRunSpec(BaseSpecRunner.java:112)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.runSpec(BaseSpecRunner.java:91)
        at org.spockframework.runtime.BaseSpecRunner.run(BaseSpecRunner.java:82)
        at org.spockframework.runtime.Sputnik.run(Sputnik.java:63)
        at org.junit.runners.Suite.runChild(Suite.java:128)
        at org.junit.runners.Suite.runChild(Suite.java:24)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
        at grails.plugin.spock.test.GrailsSpecTestType.doRun(GrailsSpecTestType.groovy:73)
        at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:290)
        at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:248)
        at _GrailsTest_groovy$_run_closure1_closure21.doCall(_GrailsTest_groovy:195)
        at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:184)
        at TestApp$_run_closure1.doCall(TestApp:82)
    
    2016-02-04 19:08:25.223 [main] grails.app.domain.org.example.domain.Ninja
     ERROR [VALIDATING] 'name': Naruto, org.example.domain.Ninja : (unsaved) FIRED!
    
    java.lang.Exception
        at org.example.domain.Ninja$__clinit__closure1_closure2.doCall(Ninja.groovy:15)
        at org.grails.datastore.gorm.GormValidationApi.doValidate(GormValidationApi.groovy:64)
        at org.grails.datastore.gorm.GormValidationApi.validate(GormValidationApi.groovy:107)
        at org.example.domain.NinjaSpec.$spock_feature_0_0(NinjaSpec.groovy:17)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.invokeFeatureMethod(BaseSpecRunner.java:285)
        at org.spockframework.runtime.BaseSpecRunner.doRunIteration(BaseSpecRunner.java:256)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.runIteration(BaseSpecRunner.java:223)
        at org.spockframework.runtime.BaseSpecRunner.initializeAndRunIteration(BaseSpecRunner.java:214)
        at org.spockframework.runtime.BaseSpecRunner.runSimpleFeature(BaseSpecRunner.java:205)
        at org.spockframework.runtime.BaseSpecRunner.doRunFeature(BaseSpecRunner.java:199)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.runFeature(BaseSpecRunner.java:175)
        at org.spockframework.runtime.BaseSpecRunner.runFeatures(BaseSpecRunner.java:152)
        at org.spockframework.runtime.BaseSpecRunner.doRunSpec(BaseSpecRunner.java:112)
        at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:138)
        at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:330)
        at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:311)
        at org.spockframework.runtime.BaseSpecRunner.runSpec(BaseSpecRunner.java:91)
        at org.spockframework.runtime.BaseSpecRunner.run(BaseSpecRunner.java:82)
        at org.spockframework.runtime.Sputnik.run(Sputnik.java:63)
        at org.junit.runners.Suite.runChild(Suite.java:128)
        at org.junit.runners.Suite.runChild(Suite.java:24)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
        at grails.plugin.spock.test.GrailsSpecTestType.doRun(GrailsSpecTestType.groovy:73)
        at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:290)
        at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:248)
        at _GrailsTest_groovy$_run_closure1_closure21.doCall(_GrailsTest_groovy:195)
        at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:184)
    
    | Completed 1 spock test, 0 failed in 2872ms
    

    因此,我只想知道:

    考虑到测试仅验证属性 sensei

    • 为什么要调用两个自定义验证器?
    • 另外,为什么要调用 transient 属性名称的自定义验证器两次?

    activation

    ====编辑:

    一件有趣的事情:

    奇怪的是,每当我在名称的自定义验证器上放置rejectValue时,例如:

    name nullable:false, bindable:true, validator: { val, obj, errors ->
        obj.log.error "[VALIDATING] 'name': $val, $obj FIRED!", new Exception()
        errors.rejectValue 'name', 'should.not.be.fired!', [].toArray(), null
    }
    

    自定义验证器只调用一次。

    但是,根本不应该调用名称的自定义验证器而不是吗?

1 个答案:

答案 0 :(得分:0)

这个问题与Spock无关,而且主要与Grails有关。

据我记得,Grails无法保证调用bean的约束的次数和次数。也许他们被召唤一次,也许是100次。

Grails测试不完全是单元测试(具有正确的术语定义)。它们在Grails引擎内运行,所以很多事情都发生在幕后。

另外需要注意的是,您已使用@TestFor注释标记了测试,但您没有使用Grails创建的测试Ninja实例,而是创建自己的(可能在Grails上下文之外创建)

我手边没有grails 2安装,但我认为正确的单元测试应该使用@TestFor创建的字段,或者使用@Mock()注释为你的Ninja类。