Show Navigation

Mock vs Stub vs Spy

By Dean Del Ponte

June 22, 2018

Tags: #spock

Introduction

Spock provides three powerful yet distinct, tools that make working with collaborators easier:

Quite often, the code that is under test is required to interact with outside units of code known as collaborators. Unit tests are most often designed to focus on testing one class in isolation through the use of Mocks, Stubs or Spies. Tests are more robust and less prone to breakage should the collaborator code evolve.

These isolated tests are less brittle and less prone to breakage should the collaborators' internals be modified.

TLDR

Mocks

Use Mocks to:

  • verify the contract between the code under test and a collaborator
  • verify the the collaborator's method is called the correct number of times
  • verify the collaborator's method is called with the correct parameters

Stubs

Use Stubs to:

  • provide a predetermined response from a collaborator
  • take a predetermined action from a collaborator, like throwing an exception

Spies

Beware of Spies. As the Spock documentation states:

Think twice before using this feature. It might be better to change the design of the code under specification.

That being said, there are situations when we must work with legacy code. This legacy code may be difficult or impossible to test with Mocks or Stubs. In this case, Spies may be our only viable option.

It's better to have legacy code that is tested with a Spy then it is to have legacy code that is not tested at all.

Use Spies to:

  • test legacy code collaborators that are too difficult or impossible to test with a Mock or Spy
  • verify the collaborator's method is called the correct number of times
  • verify the collaborator's method is called with the correct parameters
  • provide a predetermined response from a collaborator
  • take a predetermined action from a collaborator, like throwing an exception

Mocks

The power of Mocks really shines when the goal of a unit test is to validate the contract between the code under test and a collaborator.

Let's take a look at the following example where we have a controller, FooController, which utilizes FooService as a collaborator and then test the functionality with a Mock.

FooController.groovy

package com.mycompany.myapp

import groovy.transform.CompileStatic

@CompileStatic
class FooController {
    FooService fooService

    def doSomething() {
        render fooService.doSomething("Sally")
    }
}

FooService.groovy

package com.mycompany.myapp

import groovy.transform.CompileStatic

@CompileStatic
class FooService {

    String doSomething(String name) {
        "Hi ${name}, FooService did something"
    }
}

In this scenario, we want to write a test that will validate:

  • the contract between FooController and FooService
  • FooService.doSomething(name) is called the correct number of times
  • FooService.doSomething(name) is passed the correct parameter

Here's the test:

MockSpec.groovy

package com.mycompany.myapp

import grails.testing.web.controllers.ControllerUnitTest
import spock.lang.Specification

class MockSpec extends Specification implements ControllerUnitTest<FooController> {

    void "Mock FooService"() {
        given: "the collaborating service is mocked"
        def fooService = Mock(FooService)

        and: "the mocked service is set on the controller"
        controller.fooService = fooService

        when: "the controller action is called"
        controller.doSomething()

        then: "the Mock can be used to validate cardinality and parameters"
        1 * fooService.doSomething("Sally")

        and: "the mocked service returns the default 'zero value' of 'null'"
        response.text == null.toString()
    }
}

The above test mocks FooService by doing the following:

def fooService = Mock(FooService)

The test also verifies that FooService.doSomething(name) is called just once and the parameter passed to it is equal to a String value of "Sally".

1 * fooService.doSomething("Sally")

The above snippets of code accomplish four important tasks:

  • Creates a Mock for FooService
  • Ensures the FooService.doSomething(String name) method is called only once and the parameter name is equal to the String "Sally"
  • Isolates the code under test by mocking the implementation of the collaborator

Stubs

Does the code under test use a collaborator? Is the goal to ensure the code under test behaves properly when interacting with a collaborator? Does the collaborator act as input to the code under test?

If the behavior of the code under test will change based upon the behavior of the collaborator, then what you need to use is a Stub.

Let's take a look at the following example where we have a controller, FooController, which utilizes FooService as a collaborator and then test the functionality with a Stub.

FooController.groovy

package com.mycompany.myapp

import groovy.transform.CompileStatic

@CompileStatic
class FooController {
    FooService fooService

    def doSomething() {
        render fooService.doSomething("Sally")
    }
}

FooService.groovy

package com.mycompany.myapp

import groovy.transform.CompileStatic

@CompileStatic
class FooService {

    String doSomething(String name) {
        "Hi ${name}, FooService did something"
    }
}

Here's the test:

StubSpec.groovy

package com.mycompany.myapp

import grails.testing.web.controllers.ControllerUnitTest
import spock.lang.Specification

class StubSpec extends Specification implements ControllerUnitTest<FooController> {

    void "Stub FooService"() {
        given: "the collaborating service is stubbed"
        def fooService = Stub(FooService) {
            doSomething(_) >> "Stub did something"
        }

        and: "the stubbed service is set on the controller"
        controller.fooService = fooService

        when: "the controller action is called"
        controller.doSomething()

        then: "the stubbed service returns the stubbed text"
        // 1 * fooService.doSomething() cardinality not supported by Stub
        response.text == "Stub did something"
    }
}

The above test stubs FooService by doing the following:

def fooService = Stub(FooService) {
    doSomething(_) >> "Stub did something"
}

The above code accomplishes four important tasks:

  • Creates a Stub for FooService
  • Ensures the FooService.doSomething(String name) method will return the String "Stub did something" independently of the name parameter value. Hence the use of _.
  • Isolates the code under test by mocking the implementation of the collaborator

Spies

Please don't read this section.

Look away.

Please skip ahead to the next section.

Still reading this? Well, okay, let's talk about Spies.

Do not use Spies. As the Spock documentation states:

Think twice before using this feature. It might be better to change the design of the code under specification.

That being said, there are situations when we must work with legacy code. The legacy code may be difficult to test with Mocks or Stubs. In this case, Spies may the only viable option.

Spies are different than Mocks or Stubs because they do not act as a test duplicate in the same sense.

When a class is Mocked or Stubbed, a test double is created and the original code that exists within the Mocked or Stubbed object is not executed.

Spies, on the other hand, will execute the original code from which the Spy was created, but a Spy will also allow you to modify what the Spy returns and verify cardinality much like Mocks and Stubs.

Let's take a look at the following example where we have a controller, FooController, which utilizes FooService as a collaborator and then test the functionality with a Spy.

FooController.groovy

package com.mycompany.myapp

import groovy.transform.CompileStatic

@CompileStatic
class FooController {
    FooService fooService

    def doSomething() {
        render fooService.doSomething("Sally")
    }
}

FooService.groovy

package com.mycompany.myapp

import groovy.transform.CompileStatic

@CompileStatic
class FooService {

    String doSomething(String name) {
        "Hi ${name}, FooService did something"
    }
}

Here's the test:

SpySpec.groovy

package com.mycompany.myapp

import grails.testing.web.controllers.ControllerUnitTest
import spock.lang.Specification

class SpySpec extends Specification implements ControllerUnitTest<FooController> {

    void "Spy FooService"() { 
        given: "the collaborating service is a Spy"
        def fooService = Spy(FooService)

        and: "the Spy service is set on the controller"
        controller.fooService = fooService

        when: "the controller action is called"
        controller.doSomething()

        then: "the Spy can be used to validate cardinality"
        1 * fooService.doSomething("Sally") >> "A Spy can modify implementation"

        and: 'A spy can modify implementation'
        response.text == "A Spy can modify implementation"
    }
}

The above test creates a Spy on FooService by doing the following:

def fooService = Spy(FooService)
{% endhighlight %} 

The following code demonstrates how the Spy allows us to verify `FooService.doSomething(name)` is called just once and the parameter passed to it is equal to a String value of "Sally":
Moreover, it changes the method implementation to return a different String.

{% highlight groovy %}
1 * fooService.doSomething("Sally") >> "A Spy can modify implementation"

The following code demonstrates how the Spy allows us to verify FooService.doSomething(name) is called just once and the parameter passed to it is equal to a String value of "Sally": Moreover, it changes the method implementation to return a different String.

1 * fooService.doSomething("Sally") >> "A Spy can modify implementation"

The above code accomplishes four important tasks:

  • Creates a Spy for FooService
  • Verifies interaction with the collaborator.
  • Verifies how the application behaves when a collaborator responds in a certain way.

FAQ

Q: Should I use a Mock, a Stub, or a Spy?

A: This is a question faced by many a developer. The following FAQ may help if you are ever unsure as to which mocking option to use.

Q: Is the goal of the test to verify the contract between the code under test and a collaborator?
A: If you answered yes, use Mock

Q: Is the goal of the test to ensure the code under test behaves properly when interacting with a collaborator?
A: If you answered yes, use Stub

Q: Does the collaborator act as input to the code that is under test?
A: If you answered yes, use Stub

Q: Are you working with legacy code that is extremely difficult to test and you've exhausted all other options?
A: Try using a Spy

Sample Code

The sample code referenced in this post may be found at https://github.com/ddelponte/mock-stub-spy

Useful Links

You might also like ...