Given a simple domain with a transient property, such:
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()
}
}
}
And a simple Unit Tests for the domain's constraints, as follows:
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
}
}
When I run this Spec, that is just supposed to validate the non-transient property sensei, two unexpected things happen:
- Both custom validators are being called;
- Not only is the name's custom validator being called, but it is, in fact, being called twice.
Check it out:
➜ 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
Thus, what I just would like to know is:
Considering that the test only validates the property sensei:
- Why are both custom validators being called?
- Plus, why is the custom validator of the transient property name being called twice?
==== EDIT:
One interesting thing:
Curiously, whenever I put a rejectValue at the name's custom validator, for instance:
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
}
the custom validator is called just once.
But still, shouldn't the name's custom validator not be called at all?
This problem has nothing to do with Spock and it is mostly related with Grails.
As far as I remember there is no guarantee from Grails on when and how many times the constraints of a bean are called. Maybe they are called once, maybe a 100 times.
Grails tests are not exactly unit tests (with the proper definition of the term). They run inside the Grails engine and so a lot of things happen behind the scenes.
Another thing to note is that you have marked your test with the @TestFor annotation but instead of using the test Ninja instance created by Grails, you create your own (which is possibly created outside the Grails context)
I don't have a grails 2 installation at hand but I think the correct unit test should use the field created by the @TestFor or alternatively use the @Mock() annotation for your Ninja class.