Groovy Mocking with Spock: Mock, Stub, and Spy Patterns
Spock's built-in mocking eliminates the need for Mockito or PowerMock in most Groovy/Java projects. The API is concise, the error messages are clear, and the interactions integrate directly with the then block's assertion syntax.
The Three Types
Spock provides three mock variants with distinct purposes:
Mock — verifies interactions AND can stub return values. Use when you care that a method was called.
Stub — only stubs return values. Use for dependencies you need to control but don't care about call counts.
Spy — wraps a real object, delegates by default, and allows selective overrides. Use when you need the real behavior but want to verify or override specific calls.
def emailService = Mock(EmailService) // verify interactions
def userRepo = Stub(UserRepository) // just return values
def realService = Spy(NotificationService) // real + selective overrideMock: Verification + Stubbing
Mocks let you specify expected interactions in the then block:
def "order submission sends a confirmation email"() {
given:
def emailService = Mock(EmailService)
def orderService = new OrderService(emailService)
when:
orderService.submit(new Order(id: 42, email: "user@example.com"))
then:
1 * emailService.sendConfirmation("user@example.com", 42)
}Combine stubbing and verification on the same mock:
then:
1 * emailService.sendConfirmation("user@example.com", 42) >> "msg-id-123"The >> after the interaction spec provides the return value.
Stub: Controlling Dependencies
When you only need to control what a dependency returns without verifying calls:
def "service returns empty list when repository is empty"() {
given:
def repo = Stub(UserRepository)
repo.findAll() >> []
def service = new UserService(repo)
when:
def users = service.getAllUsers()
then:
users.isEmpty()
}Stubs return default values (null, 0, empty collections) unless you configure them. Configure multiple methods:
def repo = Stub(UserRepository) {
findById(1L) >> new User(id: 1, name: "Alice")
findById(2L) >> new User(id: 2, name: "Bob")
findById(_) >> null // catch-all
count() >> 2
}Spy: Wrapping Real Objects
Spies delegate to the real object by default. Override only specific methods:
def "spy delegates to real implementation"() {
given:
def service = Spy(GreetingService)
when:
def result = service.greet("World")
then:
result == "Hello, World!" // real implementation called
1 * service.greet("World") // but we can still verify
}
def "spy overrides specific method"() {
given:
def service = Spy(GreetingService)
service.greet("World") >> "Mocked greeting"
when:
def result = service.greet("World")
then:
result == "Mocked greeting"
}For spies on classes with required constructor args:
def service = Spy(new PaymentService(config, gateway))Argument Matchers
Spock provides matchers for flexible interaction verification:
then:
// Any argument
1 * service.save(_)
// Type constraint
1 * service.save(_ as User)
// Closure constraint — most powerful
1 * service.save({ User u -> u.age >= 18 && u.email.contains("@") })
// Specific value
1 * service.findById(42L)
// Not null
1 * service.process(!null)
// Multiple args — mix matchers
1 * service.transfer(_, 100.00, { it.currency == "USD" })Return Value Configuration
// Return constant
repo.find(_) >> new User()
// Return from closure
repo.find(_) >> { Long id -> new User(id: id) }
// Return different values on successive calls
repo.count() >>> [0, 1, 2]
// Throw exception
service.process(_) >> { throw new ServiceException("error") }
// Multiple behaviors: first call returns, second throws
service.load(1L) >>> [new User(), { throw new NotFoundException() }]Interaction Cardinality
then:
0 * emailService.send(_) // never
1 * emailService.send(_) // exactly once
2 * emailService.send(_) // exactly twice
(1..3) * emailService.send(_) // 1 to 3 times
(1.._) * emailService.send(_) // at least once
(_..3) * emailService.send(_) // at most 3 times
_ * emailService.send(_) // any number (0+)Ordered Interactions
Verify that methods are called in sequence:
def "payment processing happens in order"() {
given:
def gateway = Mock(PaymentGateway)
def processor = new PaymentProcessor(gateway)
when:
processor.charge(order)
then:
1 * gateway.validate(order)
then:
1 * gateway.authorize(order)
then:
1 * gateway.capture(order)
}Multiple then blocks enforce ordering.
Strict vs Lenient Verification
By default, any unexpected call to a Mock throws. To allow unexpected calls on specific interactions:
then:
1 * emailService.send(_)
_ * emailService.log(_) // allow any number of log callsOr use a Stub for dependencies you don't want to verify at all.
Mocking Final Classes and Static Methods
Spock can mock final classes via @MockMaker:
@MockMaker("mock-maker-inline") // requires byte-buddy-agent
def client = Mock(FinalHttpClient)For static methods, use GroovyStub or consider restructuring to avoid static dependencies.
Global Stubs with @Shared
Reuse stubs across all feature methods:
class UserServiceSpec extends Specification {
@Shared
def config = Stub(AppConfig) {
getMaxRetries() >> 3
getTimeout() >> 5000
}
def userRepo = Mock(UserRepository)
def service = new UserService(userRepo, config)
// all feature methods use the same config stub
}@Shared means one instance — good for stateless stubs, risky for mocks (call counts accumulate across tests).
Detecting Mock Leaks
If a mock's interaction fails verification, Spock reports:
- The expected interaction
- The actual calls made
- Where the stub was set up
Too few invocations for:
1 * emailService.sendConfirmation("user@example.com", 42) (0 invocations)
Unmatched invocations (ordered by similarity):
1 * emailService.sendConfirmation("wrong@example.com", 42)This level of detail makes diagnosing failures fast — no need to add debug logging.
When to Use Each
| Scenario | Use |
|---|---|
| Verify the dependency was called | Mock |
| Control what dependency returns | Stub |
| Partially override real behavior | Spy |
| Shared config/infrastructure | Stub with @Shared |
| Real object + call verification | Spy |
Spock's mocking system covers virtually all production use cases without plugins or additional libraries, making it the natural choice for Groovy/Java test suites.