Moki, stubs and spies in the Spock Framework

Published on January 09, 2019

Moki, stubs and spies in the Spock Framework

http://grailsblog.objectcomputing.com/posts/2018/06/22/mock-vs-stub-vs-spy.html
  • Transfer

Spock provides 3 powerful (but different in essence) tools that simplify writing tests: Mock, Stub and Spy.



Quite often, the code that needs to be tested needs to interact with external modules called dependencies (in the original article, the term collaborators is used, which is not very common in the Russian-speaking environment).


Unit tests are most often developed for testing one isolated class using various mock options: Mock, Stub and Spy. So the tests will be more reliable and will be less likely to break as the code of dependencies evolves.


Such isolated tests are less prone to problems when changing the internal details of the implementation of dependencies.


From the translator: every time I use the Spock Framework to write tests, I feel that I can be mistaken when choosing a method for replacing dependencies. This article has the shortest possible crib for choosing a mechanism for creating mocks.


TL; DR


Mocks


Use Mock to:


  • contract verification between code under test and dependencies
  • checks that dependency methods are called the correct number of times
  • checking the correctness of the parameters with which the dependency code is called

Stubs


Use Stub to:


  • providing predefined call results
  • performing predefined actions expected from dependencies, such as throwing exceptions

Spies


Fear Spyware (Spy). As stated in the Spock documentation:


Think twice before using this mechanism. Perhaps you should redesign your solution and reorganize your code.

But it just so happens that there are situations when we have to work with legacy code. Legacy code can be difficult or even impossible to test with mocks and stubs. In this case, there is only one solution - use Spy.


It is better to have legacy code covered with tests using Spy than not having any legacy tests.


Use Spy to:


  • testing legacy code that cannot be tested by other methods
  • checks that dependency methods are called the correct number of times
  • validation of transmitted parameters
  • provide a predetermined response to dependencies
  • performing predefined actions in response to calls to dependency methods

Mocks



The whole power of mocks is manifested when the task of the unit test is to verify the contract between the code being tested and the dependencies. Let's look at the following example, where we have a controller FooControllerthat uses FooServiceas a dependency, and test this functionality using mocks.


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 checks:


  • contract between FooControllerandFooService
  • FooService.doSomething(name) is called the correct number of times
  • FooService.doSomething(name) called with the correct parameter

Take a look at 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: "создаём мок зависимости"
        def fooService = Mock(FooService)
        and: "устанавливаем экземпляр мока в контроллер"
        controller.fooService = fooService
        when: "вызываем действие контроллера"
        controller.doSomething()
        then: "мок можно использовать для проверки числа вызовов и значений параметров"
        1 * fooService.doSomething("Sally")
        and: "мок возвращает 'пустое' значение по умолчанию - 'null'"
        response.text == null.toString()
    }
}

The above test creates a mock service:


def fooService = Mock(FooService)

The test also checks that it FooService.doSomething(name)is called once, and the parameter passed to it matches the string "Sally".


1 * fooService.doSomething("Sally")

The above code solves 4 important tasks:


  • creates mock for FooService
  • makes sure that it FooService.doSomething(String name)is called exactly once with a parameter Stringand a value"Sally"
  • isolates the code under test, replacing the dependency implementation

Stubs


Does the code being tested use dependencies? Is the purpose of testing to make sure that the code under test works correctly when interacting with dependencies? Are the results of the dependency method calls the input values ​​for the code under test?


If the behavior of the code being tested changes depending on the behavior of the dependencies, then you need to use stubs.


Let's look at the following example with FooControllerand FooServiceand test the functionality of the controller by using stubs.


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

Test code:


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: "создаём стаб сервиса"
        def fooService = Stub(FooService) {
            doSomething(_) >> "Stub did something"
        }
        and: "устанавливаем экземпляр стаба в контроллер"
        controller.fooService = fooService
        when: "вызываем действие контроллера"
        controller.doSomething()
        then: "стаб возвращает константное значение"
        // 1 * fooService.doSomething()
        // проверки числа вызовов не поддерживаются для стабов 
        response.text == "Stub did something"
    }
}

You can create a stub like this:


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

The above code solves 4 important tasks:


  • creates a stub FooService
  • makes sure that it FooService.doSomething(String name)returns a string "Stub did something"regardless of the parameter passed (so we used the symbol _)
  • isolates the code under test, replacing the dependency implementation with a stub

Spies


Please do not read this section.


Do not look.


Skip to the next one.


Still reading? Well, well, let's deal with Spy.



Do not use spy. As stated in the Spock documentation:


Think twice before using this mechanism. Perhaps you should redesign your solution and reorganize your code.

At the same time, there are situations when we have to work with legacy code. Legacy code can not be tested using mocks or stubs. In this case, the spy remains the only viable option.


Spies are different from mocks or stubs, because they do not work as stubs.


When a dependency is replaced by a mock or a stub, a test object is created, and the real dependency source code is not executed.


The spy, on the other hand, will execute the main source code of the dependency for which the spy was created, but the spy will allow you to change what the spy returns, and check for method calls, as well as moki and stubs. (Hence the name Spy).


Let's take a look at the following example FooControllerthat uses FooService, 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"
    }
}

Test code:


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: "создаём экземпляр-шпион"
        def fooService = Spy(FooService)
        and: "устанавливаем зависимость в контроллер"
        controller.fooService = fooService
        when: "вызываем действие контроллера"
        controller.doSomething()
        then: "проверяем число вызовов и значения параметров"
        1 * fooService.doSomething("Sally") >> "A Spy can modify implementation"
        and: 'шпион может изменять реализацию методов зависимостей'
        response.text == "A Spy can modify implementation"
    }
}

Creating a spy instance is quite simple:


def fooService = Spy(FooService)

In the above code, the spy allows us to check the call FooService.doSomething(name), the number of calls and the parameter values. Moreover, the spy modifies the implementation of the method to return a different value.


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

The above code solves 4 important tasks:


  • creates a spy instance for FooService
  • checks interaction with dependencies
  • Checks how the application works according to the specific results of the calls to the dependency method.
  • isolates the code under test, replacing the dependency implementation with a stub

FAQ


Which option to use: Mock, Stub or Spy?


This is a question that many developers face. This FAQ can help if you are unsure which approach to use.


Q: Is the goal of testing the verification of the contract between the code being tested and the dependencies?


A: If you answered yes, use mock


Q: Is the purpose of testing to make sure that the code under test works correctly when interacting with dependencies?


A: If you answered yes, use Stub


Q: Are the results of the dependency method calls the input values ​​for the code under test?


A: If you answered yes, use Stub


Q: Do you work with Legacy code that is very difficult to test, and you have no options left?


A: Try using spy


Code of examples


You can find the code for all examples of this article at the link:


https://github.com/ddelponte/mock-stub-spy


useful links