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

Дан простой домен с внедренным сервисом, который используется для выполнения определенной проверки внутри ограничения, например:

package org.example.domain

class Ninja {

    String name
    String sensei
    String village

    def ninjaService

    static transients = ['ninjaService']

    static constraints = {
        ....
        village nullable:true, validator:{ val, obj, errors ->
            obj.ninjaService.validate(obj)
        }
    }
}

И простая спецификация, которая заглушает поведение службы, например:

package org.example.domain

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

import org.example.services.NinjaService

@TestFor(Ninja)
class NinjaSpec extends Specification {

    def 'Should succeed at validating the village using a stubbed method'() {
        given:
            def instance = new Ninja(village: 'Leaf')
            instance.ninjaService = Mock(NinjaService)
        when:
            instance.validate(['village'])
        then:
            1 * instance.ninjaService.validate(instance) >> { final Ninja ninja ->
                ninja.errors.rejectValue 'village', 'should.be.an.error', [].toArray(), null
                ninja.log.debug "[NINJA SERVICE MOCK] (VALIDATING) 'village' FIRED! ninja.errors['village']?.code: '${ninja.errors['village']?.code}'"
            }
        and:
            instance.errors['village']?.code == 'should.be.an.error'
    }
}

Затем происходит то, что instance.errors['village']?.code имеет значение null. Проверьте это:

grails test-app unit:spock -clean -echoOut NinjaSpec

| Compiling 121 source files

| Running 1 spock test... 1 of 1

--Output from Should succeed at validating the village via mock--

2016-02-04 19:51:00.219 [main] grails.app.domain.org.example.domain.Ninja
 DEBUG [NINJA SERVICE MOCK] (VALIDATING) 'village' FIRED! ninja.errors['village']?.code: 'should.be.an.error'

| Failure:  Should succeed at validating the village via mock(org.example.domain.NinjaSpec)
|  Condition not satisfied:

instance.errors['village']?.code == 'should.be.an.error'
|        |     |            |    |
|        |     null         null false
|        org.grails.datastore.mapping.validation.ValidationErrors: 0 errors
org.example.domain.Ninja : (unsaved)

    at org.example.domain.NinjaSpec.Should succeed at validating the village via mock(NinjaSpec.groovy:36)

| Completed 1 Spock test, 1 failed in 2032ms

Почему экземпляр не содержит код should.be.an.error, который был установлен во взаимодействии-заглушке?

Пример проекта на github


person Rogério R. Alcântara    schedule 04.02.2016    source источник


Ответы (2)


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

person Pablo Pazos    schedule 04.02.2016
comment
Итак, на самом деле, что я хотел бы сделать, так это смоделировать поведение службы в этом тесте. Для этой тестовой цели я хотел бы игнорировать реализацию службы и моделировать ее поведение, устанавливая ошибку вручную. Любопытно то, что я пытался сделать это, даже используя instance.ninjaService.metaclass.validate = {} в блоке given, и это тоже не работает. С чего бы это? - person Rogério R. Alcântara; 05.02.2016
comment
попробуйте решение здесь stackoverflow.com/questions/25363401/ имитирует сервисные методы с замыканиями, поэтому вы можете добавить свою собственную логику, например, добавить ошибки в экземпляр Ninja. - person Pablo Pazos; 05.02.2016
comment
Спасибо @pablo-pazos, но я получил тот же результат, используя mockFor()/createMock(). Хотя замыкание/заглушка вызывается перед блоком when, кажется, что то, что происходит внутри замыкания/заглушки, просто игнорируется. Я действительно не знаю, что я делаю неправильно здесь .. - person Rogério R. Alcântara; 05.02.2016

Эта реализация, предложенная @PabloPazos с использованием mockFor() и createMock(), работает как шарм!

def 'Should succeed at validating the village via mock'() {
    given:
        def instance = new Ninja(village: 'Leaf')
        def ninjaServiceMock = mockFor(NinjaService)
        ninjaServiceMock.demand.validate { final Ninja ninja ->
            ninja.errors.rejectValue 'village', 'should.be.an.error', [].toArray(), null
        }
        instance.ninjaService = ninjaServiceMock.createMock()
    when:
        instance.validate(['village'])
    then:
        instance.errors['village']?.code == 'should.be.an.error'
}

Однако я до сих пор не понимаю, почему не работал заглушенный сервис Спока.

person Rogério R. Alcântara    schedule 05.02.2016
comment
Этот тест просто явно отвергает значение. - person Pablo Pazos; 05.02.2016
comment
Точно. Как я уже сказал, мне не очень нравится такой подход. Но это было, к сожалению, единственное, что работало. - person Rogério R. Alcântara; 05.02.2016
comment
Я понимаю, но тест не тестирует много. Пожалуйста, поделитесь кодом, который вы пробовали и не работали, чтобы посмотреть. - person Pablo Pazos; 05.02.2016
comment
Привет @PabloPazos! Я тестировал код, который я написал, реализуя ваше предложение, чтобы опубликовать его здесь, и... это сработало! Я мог сделать что-то не так в моей первой попытке. Таким образом, я собираюсь обновить этот ответ, предоставив вам кредиты, и приму ваш ответ, хорошо? Огромное спасибо! - person Rogério R. Alcântara; 05.02.2016