Spock предоставляет 3 мощных (но разных по сути) инструмента, упрощающих написание тестов: Mock, Stub и Spy.

Довольно часто коду, который нужно протестировать, требуется взаимодействовать с внешними модулями, называющимися зависимостями (в оригинальной статье используется термин collaborators, который не очень распространён в русскоязычной среде).
Модульные тесты чаще всего разрабатываются для тестирования одного изолированного класса при помощи различных вариантов моков: Mock, Stub и Spy. Так тесты будут надёжнее и будут реже ломаться по мере того, как код зависимостей эволюционирует.
Такие изолированные тесты менее подвержены проблемам при изменении внутренних деталей реализации зависимостей.
От переводчика: каждый раз, когда я использую Spock Framework для написания тестов, я чувствую, что могу ошибиться при выборе способа подмены зависимостей. В этой статье есть максимально краткая шпаргалка по выбору механизма для создания моков.
TL;DR
Mocks
Используйте Mock для:
- проверки контракта между тестируемым кодом и зависимостями
- проверки того, что методы зависимостей вызываются корректное число раз
- проверки корректности параметров, с которыми вызывается код зависимостей
Stubs
Используйте Stub для:
- предоставления предопределённых результатов вызовов
- выполнения предопределённых действий, ожидаемых от зависимостей, таких как выбрасывание исключений
Spies
Бойтесь шпионов (Spy). Как сказано в документации Spock:
Подумайте дважды, прежде чем использовать этот механизм. Возможно, вам стоит изменить дизайн вашего решения и реорганизовать ваш код.
Но так уж случается, что бывают ситуации, когда мы должны работать с легаси кодом. Легаси код бывает сложно или даже невозможно протестировать при помощи моков и стабов. В этом случае есть всего один вариант решения — использовать Spy.
Лучше иметь легаси код, покрытый тестами с использованием Spy, чем не иметь тестов для легаси совсем.
Используйте Spy для:
- тестирования легаси кода, который невозможно протестировать другими методами
- проверки того, что методы зависимостей вызываются корректное число раз
- проверки корректности передаваемых параметров
- предоставления предопределённого ответа от зависимостей
- выполнения предопределённых действий в ответ на вызовы методов зависимостей
Mocks

Вся сила моков проявляется, когда задача модульного теста состоит в проверке контракта между тестируемым кодом и зависимостями. Давайте посмотрим на следующий пример, где у нас имеется контроллер FooController, который использует FooService в качестве зависимости, и протестируем эту функциональность при помощи моков.
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" } }
В этом сценарии мы хотим написать тест, который проверит:
- контракт между
FooControllerиFooService FooService.doSomething(name)вызывается корректное число разFooService.doSomething(name)вызывается с корректным параметром
Взглянем на тест:
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() } }
Приведённый тест создаёт мок сервиса:
def fooService = Mock(FooService)
Также тест проверяет, что FooService.doSomething(name) вызывается один раз, и параметр, переданный в него, совпадает со строкой "Sally".
1 * fooService.doSomething("Sally")
Приведённый код решает 4 важные задачи:
- создаёт мок для
FooService - убеждается в том, что
FooService.doSomething(String name)вызывается ровно один раз с параметромStringи значением"Sally" - изолирует тестируемый код, заменяя реализацию зависимости
Stubs
Использует ли тестируемый код зависимости? Является ли целью тестирования удостовериться, что тестируемый код работает корректно при взаимодействии с зависимостями? Являются ли результаты вызовов методов зависимостей входными значениями для тестируемого кода?
Если поведение тестируемого кода изменяется в зависимости от поведения зависимостей, то вам необходимо использовать стабы (Stub).
Давайте посмотрим на следующий пример с FooController и FooService и протестируем функциональность контроллера при помощи стабов.
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" } }
Код теста:
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" } }
Создать стаб можно так:
def fooService = Stub(FooService) { doSomething(_) >> "Stub did something" }
Приведённый код решает 4 важные задачи:
- создаёт стаб
FooService - убеждается в том, что
FooService.doSomething(String name)вернёт строку"Stub did something"независимо от переданного параметра (поэтому мы использовали символ_) - изолирует тестируемый код, заменяя реализацию зависимости на стаб
Spies
Пожалуйста не читайте этот раздел.
Не смотрите.
Пропускайте и переходите к следующему.
Всё ещё читаете? Ну что ж, хорошо, давайте разбираться со Spy.

Не используйте Spy. Как сказано в документации Spock:
Подумайте дважды, прежде чем использовать этот механизм. Возможно, вам стоит изменить дизайн вашего решения и реорганизовать ваш код.
При этом бывают ситуации, когда нам приходится работать с легаси кодом. Легаси код бывает невозможно протестировать с помощью моков или стабов. В этом случае шпион остаётся единственным жизнеспособным вариантом.
Шпионы отличаются от моков или стабов, потому что они работают не как заглушки.
Когда зависимость подменяется моком или стабом, создается тестовый объект, а настоящий исходный код зависимости не выполняется.
Шпион, с другой стороны, выполнит основной исходный код зависимости, для которой был создан шпион, но шпион позволит вам изменять то, что возвращает шпион, и проверять вызовы методов, так же как моки и стабы. (Отсюда и название Spy).
Давайте посмотрим на следующий пример FooController, который использует FooService, а затем протестируем функциональность с помощью шпиона.
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" } }
Код теста:
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" } }
Создать экземпляр-шпион довольно просто:
def fooService = Spy(FooService)
В приведённом коде шпион позволяет нам проверить вызов FooService.doSomething(name), количество вызовов и значения параметров. Более того, шпион изменяет реализацию метода, чтобы вернуть другое значение.
1 * fooService.doSomething("Sally") >> "A Spy can modify implementation"
Приведённый код решает 4 важные задачи:
- создаёт экземпляр шпион для
FooService - проверяет взаимодействие с зависимостями
- проверяет, как приложение работает в соответствии с определёнными результатами вызовов методов зависимостей
- изолирует тестируемый код, заменяя реализацию зависимости на стаб
FAQ
Какой из вариантов использовать: Mock, Stub или Spy?
Это вопрос, с которым сталкиваются многие разработчики. Этот FAQ может помочь, если вы не уверены, какой из подходов использовать.
Q: Является ли целью тестирования проверка контракта между тестируемым кодом и зависимостями?
A: Если вы ответили Да, используйте Mock
Q: Является ли целью тестирования удостовериться, что тестируемый код работает верно при взаимодействии с зависимостями?
A: Если вы ответили Да, используйте Stub
Q: Являются ли результаты вызовов методов зависимостей входными значениями для тестируемого кода?
A: Если вы ответили Да, используйте Stub
Q: Работаете ли вы с легаси кодом, который очень сложно протестировать, и у вас не осталось вариантов?
A: Попробуйте использовать Spy
Код примеров
Вы можете найти код всех примеров этой статьи по ссылке:
https://github.com/ddelponte/mock-stub-spy
