SpockFramework的全局Mocks不能如期工作。

问题描述 投票:1回答:1

我以下面的测试为例来展示我看到的类似问题。我认为这只是我对SpockFramework中全局mocks如何工作的一个误解。

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

这个测试应该创建一个测试阶段,提供一个默认的名称。但是我在SpockFramework中创建了一个类的全局mock。TestStage 来覆盖返回值。IE: 我只是想测试一下 TestStageTestStep. 如果 TestStep 我不想知道这些变化,我会单独测试这些。然而当我运行这个测试时,看起来全局的mock从来没有生效过,因为返回的名称仍然是 "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
    }
}
java unit-testing groovy spock
1个回答
2
投票

事实上,你问了一个很好的问题,因为根据Spock手册,似乎你可以使用 GroovyMockGroovyStub 为了在全局范围内替换实例,并像你试图做的那样,把它们的方法存根化,尽管如果我是你的话,我会先创建全局mock对象,然后再在依赖于它的对象的构造函数中隐式使用它。但无论如何,它并没有像你说的那样工作。

当我在Spock手册和Spock源码中搜索关于 GroovyMock,我没有在任何地方找到一个涉及除了静态方法以外的其他方法。实际上,那里的测试覆盖率相当糟糕。通常如果手册不能帮助我,我就会看是否能从测试中推断出如何使用一个功能。在这种情况下,我不得不自己尝试。

我首先注意到的是一个完全违背直觉的事实,那就是 当调用一个全局的 GroovyMockGroovyStub,它返回 null!!! 这是一个真正的警告。在某种程度上,构造函数在这里被当作普通的mock方法对待,也返回了 null. 任何官方来源都没有提到这一点,我也认为应该将其改为默认返回一个正常的Spock mock来代替(如果该类是可模拟的,即非最终的)。

现在,这也是关键所在。的解决方案。你需要用一个或多个构造函数来返回一些其他的东西,而不是... ... null, 例如,一个先前创建的普通实例或一个Spock mockstubspy。

下面是你的源代码的一个小改动版本(我重命名了应用类,以便在它们的名字中不包含 "Test",因为所有这些 "Test "对我来说有点混乱,尤其是我还把我的测试类命名为 "*Test",就像我通常做的那样,而不是 "*Spec",因为这样Maven SurefireFailsafe就会自动接收它,而不需要额外的配置。

我还在类中添加了一个静态方法来进行模拟,以便向你展示支管的语法,这只是一个免费的附加功能,与你的问题没有直接关系。

我的测试显示了三种变体。

  • 使用一个经典的Spock mock 并把它注入到被测试的对象中去
  • 使用全局性 GroovySpy 它总是基于一个真实的对象(或被指示创建一个),因此你不需要支管一个对立面。
  • 使用全局性 GroovyMock 用一个显式的存根构造函数,在我的例子中,返回一个带有存根方法的常规Spock mock,但它也可以返回一个普通的实例。
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"
  }

}

P.S.: 我想你可能读过Spock手册,但以防万一。如果你的 GroovyMock/Stub/Spy 目标是用Groovy以外的语言实现的,如Java或Kotlin,这将无法实现,因为这样一来 Groovy* 会像普通的Spock mubstspy那样表现。


更新。 我刚刚创建了 Spock issue #1159 为了让这种行为首先被记录下来,并通过测试和改变,如果它不是这样打算的。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.