我以下面的测试为例来展示我看到的类似问题。我认为这只是我对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: 我只是想测试一下 TestStage
不 TestStep
. 如果 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
}
}
事实上,你问了一个很好的问题,因为根据Spock手册,似乎你可以使用 GroovyMock
和 GroovyStub
为了在全局范围内替换实例,并像你试图做的那样,把它们的方法存根化,尽管如果我是你的话,我会先创建全局mock对象,然后再在依赖于它的对象的构造函数中隐式使用它。但无论如何,它并没有像你说的那样工作。
当我在Spock手册和Spock源码中搜索关于 GroovyMock
,我没有在任何地方找到一个涉及除了静态方法以外的其他方法。实际上,那里的测试覆盖率相当糟糕。通常如果手册不能帮助我,我就会看是否能从测试中推断出如何使用一个功能。在这种情况下,我不得不自己尝试。
我首先注意到的是一个完全违背直觉的事实,那就是 当调用一个全局的 GroovyMock
或 GroovyStub
,它返回 null
!!! 这是一个真正的警告。在某种程度上,构造函数在这里被当作普通的mock方法对待,也返回了 null
. 任何官方来源都没有提到这一点,我也认为应该将其改为默认返回一个正常的Spock mock来代替(如果该类是可模拟的,即非最终的)。
现在,这也是关键所在。的解决方案。你需要用一个或多个构造函数来返回一些其他的东西,而不是... ... null
, 例如,一个先前创建的普通实例或一个Spock mockstubspy。
下面是你的源代码的一个小改动版本(我重命名了应用类,以便在它们的名字中不包含 "Test",因为所有这些 "Test "对我来说有点混乱,尤其是我还把我的测试类命名为 "*Test",就像我通常做的那样,而不是 "*Spec",因为这样Maven SurefireFailsafe就会自动接收它,而不需要额外的配置。
我还在类中添加了一个静态方法来进行模拟,以便向你展示支管的语法,这只是一个免费的附加功能,与你的问题没有直接关系。
我的测试显示了三种变体。
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 为了让这种行为首先被记录下来,并通过测试和改变,如果它不是这样打算的。