SpockFramework Global Mocks не работает должным образом - PullRequest
1 голос
/ 07 мая 2020

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

  void "test"() {
    when:
    TestStage stage = new TestStage("John")
    GroovyMock(TestStep.class, global: true) {
      getName() >> "Joe"
    }
    then:
    stage.run() == "Joe"
  }

Этот тест должен создать тестовую стадию с именем по умолчанию. Но затем я создаю глобальный макет класса внутри TestStage, чтобы переопределить возвращаемое значение. IE: Я только пытаюсь проверить работоспособность TestStage, а не TestStep. Если бы TestStep вносил только изменения, я не хочу знать о них, я протестирую их отдельно. Однако, когда я запускаю этот тест, похоже, что глобальный макет никогда не вступает в силу, поскольку возвращаемое имя по-прежнему "John", что я и предоставил изначально.

stage.run() == "Joe"
|     |     |
|     John  false

Вот два примера классов, используемых для тестирования это.

class TestStage {
  TestStep step

  TestStage(String name) {
    this.step = new TestStep(name)
  }

  String run() {
    return step.getName()
  }

}
class TestStep {
    private String name

    TestStep(String name) {
      this.name = name
    }

    String getName() {
      return this.name
    }
}

1 Ответ

2 голосов
/ 08 мая 2020

На самом деле, вы задаете здесь хороший вопрос, потому что, согласно руководству Spock, кажется, что вы могли бы использовать GroovyMock и GroovyStub для глобальной замены экземпляров и заглушки их методов, как вы пытались сделать, даже если на вашем месте я бы сначала создал глобальный фиктивный объект, прежде чем неявно использовать его в конструкторе объекта в зависимости от него. Но в любом случае, как вы и сказали, это не работает так, как ожидалось.

Когда я искал в руководстве Spock и исходном коде Spock примеры, касающиеся GroovyMock, нигде я не нашел ни одного, включающего что-либо иное, кроме stati c методов. На самом деле, покрытие там довольно плохое. Обычно, если руководство мне не помогает, я смотрю, смогу ли я сделать вывод из тестов, как использовать функцию. В этом случае мне пришлось попробовать самому.

Первое, что я заметил, это совершенно противоречивый факт, что при вызове конструктора в глобальном GroovyMock или GroovyStub возвращает null !!! Это настоящий нюанс. В некотором смысле конструкторы здесь обрабатываются как обычные фиктивные методы, также возвращающие null. Нигде в официальных источниках это не упоминается, и я также думаю, что его следует изменить на значение по умолчанию, чтобы вместо этого возвращался обычный макет Spock (если класс является макетом, то есть не окончательным).

Теперь это также ключ к Решение : вам нужно заглушить один или несколько конструкторов, чтобы вернуть что-то еще, кроме null, , например, ранее созданный нормальный экземпляр или mock / stub / spy Spock.

Вот слегка измененная версия вашего исходного кода (я переименовал классы приложений, чтобы не содержать «Test» в их именах, потому что все эти «Test» меня немного сбивали с толку, особенно потому, что я также назвал свой тестовый класс «* Test» , как я обычно это делаю вместо «* Spe c», потому что тогда Maven Surefire / Failsafe подберет его автоматически без дополнительной настройки.

Я также добавил метод stati c к классу, который будет издеваться чтобы показать вам синтаксис заглушки. это просто бесплатное дополнение, не имеющее прямого отношения к вашему вопросу.

Мой тест показывает три варианта:

  • с использованием классического имита Спока и инъекцией его испытуемому
  • с использованием глобального GroovySpy, который всегда основан на реальном объекте (или получил указание создать one), и, следовательно, вам не нужно заглушать конструктор
  • , используя глобальный GroovyMock с явно заглушенным конструктором, в моем примере, возвращающий обычный макет Spock с заглушенным методом, но он также может возвращать нормальный instance.
package de.scrum_master.stackoverflow.q61667088

class Step {
  private String name

  Step(String name) {
    this.name = name
  }

  String getName() {
    return this.name
  }

  static String staticMethod() {
    return "original"
  }
}
package de.scrum_master.stackoverflow.q61667088

class Stage {
  Step step

  Stage(String name) {
    this.step = new Step(name)
  }

  String run() {
    return step.getName()
  }
}
package de.scrum_master.stackoverflow.q61667088

import spock.lang.Specification

class GlobalMockTest extends Specification {

  def "use Spock mock"() {
    given:
    def step = Mock(Step) {
      getName() >> "Joe"
    }
    def stage = new Stage("John")
    stage.step = step

    expect:
    stage.run() == "Joe"
  }

  def "use global GroovySpy"() {
    given:
    GroovySpy(Step, global: true) {
      getName() >> "Joe"
    }
    Step.staticMethod() >> "stubbed"
    def stage = new Stage("John")

    expect:
    Step.staticMethod() == "stubbed"
    stage.run() == "Joe"
  }

  def "use global GroovyMock"() {
    given:
    GroovyMock(Step, global: true)
    new Step(*_) >> Mock(Step) {
      getName() >> "Joe"
    }
    Step.staticMethod() >> "stubbed"
    def stage = new Stage("John")

    expect:
    Step.staticMethod() == "stubbed"
    stage.run() == "Joe"
  }

}

PS: Я думаю, вы, вероятно, читали руководство Spock, но на всякий случай: если ваша цель GroovyMock/Stub/Spy реализована на языке, отличном от Groovy например, Java или Kotlin, это не сработает, потому что тогда Groovy* будет вести себя как обычный mock / stub / spy Spock.


Обновление: Я только что создал проблему Спока # 1159 для того, чтобы это поведение было задокументировано и покрыто тестами, а также изменилось, если оно не предназначалось для этого.

...