给定一个具有瞬态属性的简单域,例如:
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,发生了两件意想不到的事情:
- 两个自定义验证器都被调用;
- 不仅名称的自定义验证器被调用,而且实际上被调用了两次。
一探究竟:
➜ 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:
- 为什么调用两个自定义验证器?
- 另外,为什么瞬态属性名称的自定义验证器被调用两次?
==== 编辑:
一件有趣的事:
奇怪的是,每当我在名称的自定义验证器上放置一个 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
}
自定义验证器只被调用一次。
但是,不应该根本不调用名称的自定义验证器吗?