ChatGPT解决这个技术问题 Extra ChatGPT

Difference between Mock / Stub / Spy in Spock test framework

I don't understand the difference between Mock, Stub, and Spy in Spock testing and the tutorials I have been looking at online don't explain them in detail.


k
kriegaex

Attention: I am going to oversimplify and maybe even slightly falsify in the upcoming paragraphs. For more detailed info see Martin Fowler's website.

A mock is a dummy class replacing a real one, returning something like null or 0 for each method call. You use a mock if you need a dummy instance of a complex class which would otherwise use external resources like network connections, files or databases or maybe use dozens of other objects. The advantage of mocks is that you can isolate the class under test from the rest of the system.

A stub is also a dummy class providing some more specific, prepared or pre-recorded, replayed results to certain requests under test. You could say a stub is a fancy mock. In Spock you will often read about stub methods.

A spy is kind of a hybrid between real object and stub, i.e. it is basically the real object with some (not all) methods shadowed by stub methods. Non-stubbed methods are just routed through to the original object. This way you can have original behaviour for "cheap" or trivial methods and fake behaviour for "expensive" or complex methods.

Update 2017-02-06: Actually user mikhail's answer is more specific to Spock than my original one above. So within the scope of Spock, what he describes is correct, but that does not falsify my general answer:

A stub is concerned with simulating specific behaviour. In Spock this is all a stub can do, so it is kind of the simplest thing.

A mock is concerned with standing in for a (possibly expensive) real object, providing no-op answers for all method calls. In this regard, a mock is simpler than a stub. But in Spock, a mock can also stub method results, i.e. be both a mock and a stub. Furthermore, in Spock we can count how often specific mock methods with certain parameters have been called during a test.

A spy always wraps a real object and by default routes all method calls to the original object, also passing through the original results. Method call counting also works for spies. In Spock, a spy can also modify the behaviour of the original object, manipulating method call parameters and/or results or blocking the original methods from being called at all.

Now here is an executable example test, demonstrating what is possible and what is not. It is a bit more instructive than mikhail's snippets. Many thanks to him for inspiring me to improve my own answer! :-)

package de.scrum_master.stackoverflow

import org.spockframework.mock.TooFewInvocationsError
import org.spockframework.runtime.InvalidSpecException
import spock.lang.FailsWith
import spock.lang.Specification

class MockStubSpyTest extends Specification {

  static class Publisher {
    List<Subscriber> subscribers = new ArrayList<>()

    void addSubscriber(Subscriber subscriber) {
      subscribers.add(subscriber)
    }

    void send(String message) {
      for (Subscriber subscriber : subscribers)
        subscriber.receive(message);
    }
  }

  static interface Subscriber {
    String receive(String message)
  }

  static class MySubscriber implements Subscriber {
    @Override
    String receive(String message) {
      if (message ==~ /[A-Za-z ]+/)
        return "ok"
      return "uh-oh"
    }
  }

  Subscriber realSubscriber1 = new MySubscriber()
  Subscriber realSubscriber2 = new MySubscriber()
  Publisher publisher = new Publisher(subscribers: [realSubscriber1, realSubscriber2])

  def "Real objects can be tested normally"() {
    expect:
    realSubscriber1.receive("Hello subscribers") == "ok"
    realSubscriber1.receive("Anyone there?") == "uh-oh"
  }

  @FailsWith(TooFewInvocationsError)
  def "Real objects cannot have interactions"() {
    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then:
    2 * realSubscriber1.receive(_)
  }

  def "Stubs can simulate behaviour"() {
    given:
    def stubSubscriber = Stub(Subscriber) {
      receive(_) >>> ["hey", "ho"]
    }

    expect:
    stubSubscriber.receive("Hello subscribers") == "hey"
    stubSubscriber.receive("Anyone there?") == "ho"
    stubSubscriber.receive("What else?") == "ho"
  }

  @FailsWith(InvalidSpecException)
  def "Stubs cannot have interactions"() {
    given: "stubbed subscriber registered with publisher"
    def stubSubscriber = Stub(Subscriber) {
      receive(_) >> "hey"
    }
    publisher.addSubscriber(stubSubscriber)

    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then:
    2 * stubSubscriber.receive(_)
  }

  def "Mocks can simulate behaviour and have interactions"() {
    given:
    def mockSubscriber = Mock(Subscriber) {
      3 * receive(_) >>> ["hey", "ho"]
    }
    publisher.addSubscriber(mockSubscriber)

    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then: "check interactions"
    1 * mockSubscriber.receive("Hello subscribers")
    1 * mockSubscriber.receive("Anyone there?")

    and: "check behaviour exactly 3 times"
    mockSubscriber.receive("foo") == "hey"
    mockSubscriber.receive("bar") == "ho"
    mockSubscriber.receive("zot") == "ho"
  }

  def "Spies can have interactions"() {
    given:
    def spySubscriber = Spy(MySubscriber)
    publisher.addSubscriber(spySubscriber)

    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then: "check interactions"
    1 * spySubscriber.receive("Hello subscribers")
    1 * spySubscriber.receive("Anyone there?")

    and: "check behaviour for real object (a spy is not a mock!)"
    spySubscriber.receive("Hello subscribers") == "ok"
    spySubscriber.receive("Anyone there?") == "uh-oh"
  }

  def "Spies can modify behaviour and have interactions"() {
    given:
    def spyPublisher = Spy(Publisher) {
      send(_) >> { String message -> callRealMethodWithArgs("#" + message) }
    }
    def mockSubscriber = Mock(MySubscriber)
    spyPublisher.addSubscriber(mockSubscriber)

    when:
    spyPublisher.send("Hello subscribers")
    spyPublisher.send("Anyone there?")

    then: "check interactions"
    1 * mockSubscriber.receive("#Hello subscribers")
    1 * mockSubscriber.receive("#Anyone there?")
  }
}

The difference between mock and stub is not clear here. With mocks, one wants to verify behaviour (if and how many times the method is going to be called). With stubs, one verifies only state (e.g. size of collection after test). FYI: mocks can provide prepared results as well.
Thanks @mikhail and chipiik for your feedback. I have updated my answer, hopefully improving and clarifying a few things I originally wrote. Disclaimer: In my original answer I did say I was oversimplifying and slightly falsifying some Spock-related facts. I wanted people to understand the basic differences betweedn stubbing, mocking and spying.
@chipiik, one more thing as a reply to your comment: I have been coaching development teams for many years and seen them use Spock or other JUnit with other mock frameworks. In most cases when using mocks, they did not do it in order to verify behaviour (i.e. count method calls) but to isolate the subject under test from its environment. Interaction counting IMO is just an add-on goodie and should be used thoughtfully and sparingly because there is a tendency for such tests to break when they test the wiring of components more than their actual behaviour.
Its brief but still very helpful answer
m
mikhail

The question was in the context of the Spock framework and I don't believe the current answers take this into account.

Based on Spock docs (examples customized, my own wording added):

Stub: Used to make collaborators respond to method calls in a certain way. When stubbing a method, you don’t care if and how many times the method is going to be called; you just want it to return some value, or perform some side effect, whenever it gets called.

subscriber.receive(_) >> "ok" // subscriber is a Stub()

Mock: Used to describe interactions between the object under specification and its collaborators.

def "should send message to subscriber"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("hello") // subscriber is a Mock()
}

A Mock can act as a Mock and a Stub:

1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock()

Spy: Is always based on a real object with original methods that do real things. Can be used like a Stub to change return values of select methods. Can be used like a Mock to describe interactions.

def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])

def "should send message to subscriber"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("message1") >> "ok" // subscriber is a Spy(), used as a Mock an Stub
}

def "should send message to subscriber (actually handle 'receive')"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("message1") // subscriber is a Spy(), used as a Mock, uses real 'receive' function
}

Summary:

A Stub() is a Stub.

A Mock() is a Stub and Mock.

A Spy() is a Stub, Mock and Spy.

Avoid using Mock() if Stub() is sufficient.

Avoid using Spy() if you can, having to do so could be a smell and hints at incorrect test or incorrect design of object under test.


Just to add: Another reason you want to minimize the use of mocks, is that a mock is very similar to an assert, in that you check things on a mock that may fail the test, and you always want to minimize the amount of checks you do in a test, to keep the test focused and simple. So ideally there should only be one mock per test.
"A Spy() is a Stub, Mock and Spy." this isn't true for sinon spies?
I just took a quick look at Sinon spies and they look like they do not behave as Mocks or Stubs. Note that this question/answer are in the context of Spock, which is Groovy, not JS.
This should be the correct answer, as this is scoped to Spock context. Also, saying that a stub is a fancy mock might be misleading, as a mock has extra functionality (checking invocation count) that the stub does not (mock > fancier than stub). Again, mock and stubs as per Spock.
G
GKS

In simple terms:

Mock: You mock a type and on the fly you get an object created. Methods in this mock object returns the default values of return type.

Stub: You create a stub class where methods are redefined with definition as per your requirement. Ex: In real object method you call and external api and return the username against and id. In stubbed object method you return some dummy name.

Spy: You create one real object and then you spy it. Now you can mock some methods and chose not to do so for some.

One usage difference is you can not mock method level objects. whereas you can create a default object in method and then spy on it to get the desired behavior of methods in spied object.


M
More Than Five

Stubs are really only to facilitate the unit test, they are not part of the test. Mocks, are part of the test, part of the verification, part of the pass / fail.

So, say you have a method that takes in a object as a parameter. You never do anything which changes this parameter in the test. You simply read a value from it. That's a stub.

If you change anything, or need to verify some sort of interaction with the object, then it it is a mock.


I think that object is called dummy and not stub