Spock: can an interaction defined in setup() be replaced in a test case?
Asked Answered
P

1

46

I'm struggling to understand something about Spock interactions in a Groovy unit test.

I have the following types:

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();
  }
}

and I then wrote the following Groovy/Spock test:

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")
  }
}

The code creates a mock Bar instance in the setup, initializes Bar.getMessage() to return hello, and assigns this to a new Foo instance.

The first test verifies that foo.getMessage() is equal to hello.

The second test tries to modify the bar mock so that it's getMessage method returns goodbye. We then expect that foo.getMessage() (which delegates to bar.getMessage()) would then return goodbye. However the test fails as follows:

FooSpec:say goodbye:26 Condition not satisfied

because foo.message is still equal to hello.

I also tried the following:

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

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

and:

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

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

But both failed with the same hello does not equal goodbye message.

I'm probably still thinking in Mockito mode, and assume that an interaction is the equivalent of a when(...).thenReturn(...) expression, and that later interactions would override earlier interactions.

Is there a simple way using Spock to declare an interaction in a setup method, then override that interaction in a test case? Or do I need to remove the setup() method and basically add a setup: block to each test case?

Philis answered 6/3, 2014 at 5:38 Comment(0)
A
49

That's a tricky one. As stated in the docs, interactions declared in a then-block have precedence over interactions declared earlier. However, interactions declared in a then-block are scoped to the previous when-block. (This allows to have multiple when-then pairs.) Hence your last try doesn't work, but the following will:

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

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

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

I agree that it would be good for interactions declared in test methods to always override interactions declared in setup methods. Anyway, a good alternative to overriding interactions is for each test method to call a helper method that sets up the expected interactions for that test method.

Anorthite answered 6/3, 2014 at 10:22 Comment(4)
Thanks for the comment. I'm going to eschew using a setup() method and change my test methods to initialize their state directly (this is also motivated by the fact that I often use a where: block in my test methods, and found to my surprise that the setup() method was called after the where: block). I think I can understand your example above, but it's not intuitive (it still looks like setup code in the then: block to me).Philis
@PeterNiederwieser, what Spock version have you used in this example? We've tried this and a lot of other variations with Spock 1.0, but it didn't work.Bennybenoit
@JohnQCitizen I often use this tactic when I want to both assert that a service method has been called a certain number of times, as well as controlling what data is returned. I think in this example, I would put it in a given: block.Merv
Answer is correct, to override behavior you have to use then: block - even given: block does not work (which is bad imo)Aseptic

© 2022 - 2024 — McMap. All rights reserved.