Mock vs Stub vs Spy
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
andFooService
FooService.doSomething(name)
is called the correct number of timesFooService.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 parametername
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 thename
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
- https://spockframework.org/spock/docs/1.1/all_in_one.html#_interaction_based_testing
- https://martinfowler.com/bliki/UnitTest.html
- https://martinfowler.com/articles/mocksArentStubs.html