Spock:在测试用例中,能否替换在setup()中定义的交互?

46

我在理解Groovy单元测试中的Spock交互方面遇到了困难。

我有以下类型:

public interface Bar {
  public String getMessage();
}

public class Foo {
  private Bar bar;
  public void setBar(Bar bar) {
    this.bar = bar;
  }
  public String getMessage() {
    return bar.getMessage();
  }
}

我接着编写了下面这个Groovy/Spock测试:

class FooSpec extends Specification {

  private Bar bar;
  private Foo foo;

  def setup() {
    bar = Mock(Bar) { getMessage() >> "hello" }
    foo = new Foo()
    foo.bar = bar
  }

  def "say hello"() {
    expect:
    foo.message.equals("hello")
  }

  def "say goodbye"() {
    setup:
    bar.getMessage() >> "goodbye"

    expect:
    foo.message.equals("goodbye")
  }
}
代码在设置中创建了一个模拟的 Bar 实例,将 Bar.getMessage() 初始化为返回 hello,并将其分配给一个新的 Foo 实例。
第一个测试验证 foo.getMessage() 是否等于 hello
第二个测试试图修改 bar 模拟,使其 getMessage 方法返回 goodbye。然后我们期望 foo.getMessage() (委托给 bar.getMessage())将返回 goodbye。然而测试失败如下: FooSpec:say goodbye:26 Condition not satisfied 因为 foo.message 仍然等于 hello
我还尝试过以下内容:
def "say goodbye"() {
  when:
  bar.getMessage() >> "goodbye"

  then:
  foo.message.equals("goodbye")
}

而且:

def "say goodbye"() {
  when:
  no_op()

  then:
  bar.getMessage() >> "goodbye"
  foo.message.equals("goodbye")
}

但两者都出现了相同的“hello does not equal goodbye”的错误信息。

我可能还在以Mockito的模式思考,并假设一个交互就是等价于一个when(...).thenReturn(...)表达式,后续的交互会覆盖之前的交互。

是否有一种简单的方式可以使用Spock在setup方法中声明一个交互,然后在测试用例中覆盖该交互?还是说我需要删除setup()方法,基本上为每个测试用例添加一个setup:块?

1个回答

48

这是一个棘手的问题。如文档中所述,声明在then块中的交互优先于先前声明的交互。然而,在then块中声明的交互仅适用于前一个when块。(这允许有多个when-then对。) 因此,您的最后一次尝试不起作用,但以下内容将起作用:

def setup() {
    bar.message >> "hello"
}

def "say goodbye"() {
    when:
    def msg = foo.message

    then:
    bar.message >> "goodbye"
    msg == "goodbye"
}

我同意在测试方法中声明的交互应该始终覆盖在设置方法中声明的交互。无论如何,覆盖交互的一个好的替代方案是让每个测试方法调用一个帮助方法,为该测试方法设置预期的交互。


感谢您的评论。我将避免使用 setup() 方法并更改我的测试方法以直接初始化它们的状态(这也是因为我经常在测试方法中使用 where: 块,并惊讶地发现 setup() 方法在 where: 块之后被调用)。我认为我能够理解您上面的示例,但它不直观(对我来说它仍然看起来像then:块中的设置代码)。 - John Q Citizen
@PeterNiederwieser,您在这个例子中使用了哪个版本的 Spock?我们已经尝试过使用Spock 1.0和许多其他变体,但都没有成功。 - falsarella
@JohnQCitizen 当我想要断言一个服务方法被调用了特定次数并控制返回的数据时,我经常使用这种策略。在这个例子中,我认为我会将它放在given:块中。 - Zorobay
答案是正确的,要覆盖行为,您必须使用 then: 块 - 即使给定:块不起作用(在我看来很糟糕)。 - Antoniossss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接