Groovy Mocking with Spock: Mock, Stub, and Spy Patterns

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 override

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

Or 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.

Read more