Grails 2.2.4: временное свойство: почему пользовательский валидатор вызывается дважды?

Учитывая простой домен с временным свойством, такой:

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
    }
}

Когда я запускаю этот Spec, который, как предполагается, должен проверять непереходный сенсей свойства, происходят две неожиданные вещи:

  1. Вызываются оба пользовательских валидатора;
  2. Вызывается не только пользовательский валидатор имени, но фактически он вызывается дважды.

Проверьте это:

➜  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:

  • Почему вызываются оба пользовательских валидатора?
  • Плюс, почему пользовательский валидатор временного имени свойства вызывается дважды?

Образец проекта @ github

==== РЕДАКТИРОВАТЬ:

Одна интересная вещь:

Любопытно, что всякий раз, когда я помещаю 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 ответ

Эта проблема не имеет ничего общего со Споком и в основном связана с Граилсом.

Насколько я помню, от Grails нет гарантии, когда и сколько раз будут вызваны ограничения бина. Может быть, их называют один раз, может быть, 100 раз.

Тесты Грааля не совсем единичные тесты (при правильном определении термина). Они работают внутри двигателя Grails, и за кулисами происходит много вещей.

Следует также отметить, что вы пометили свой тест аннотацией @TestFor, но вместо использования тестового экземпляра Ninja, созданного Grails, вы создаете свой собственный (который, возможно, создается вне контекста Grails).

У меня под рукой нет установки Grails 2, но я думаю, что для правильного модульного теста следует использовать поле, созданное @TestFor, или использовать аннотацию @Mock() для вашего класса Ninja.

Другие вопросы по тегам