📜 ⬆️ ⬇️

Moki, stubs and spies in the Spock Framework

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:



Stubs


Use Stub to:



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:



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 FooController controller that uses FooService as 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:



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



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 FooController and FooService and test the functionality of the controller 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:



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 FooController example, which 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 verify the call to FooService.doSomething(name) , the number of calls and the values ​​of the parameters. 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:



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



')

Source: https://habr.com/ru/post/435488/


All Articles