Grails 2.2.4: Transient Property: Why is the Custom Validator being called twice?

331 Views Asked by At

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:

  1. Both custom validators are being called;
  2. 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?

Sample Project @ github

==== 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?

1

There are 1 best solutions below

0
On

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.