Spock Mocking: Stubs, Mocks, and Interaction Verification

Spock Mocking: Stubs, Mocks, and Interaction Verification

Spock's mocking is one of its strongest features. Unlike Mockito, where you set up stubs and verify interactions using separate API calls (when(...).thenReturn(...) and verify(...)), Spock combines stubbing and interaction verification into its block syntax. The result is specifications that read like requirements rather than API calls.

Mock vs Stub vs Spy

Spock distinguishes three types of test doubles with different purposes:

Mock() — records interactions. Use when you want to verify that a method was called with specific arguments. Unexpected interactions are allowed but you can assert on them.

Stub() — returns configured values. Use when you only care about what the collaborator returns, not whether it was called. Stubbed methods return default values unless configured.

Spy() — wraps a real object. Calls the real method unless stubbed. Use for partial mocking when you want most of the real behavior but need to control specific methods.

class PaymentServiceSpec extends Specification {

    // Mock — we'll verify interactions
    PaymentGateway gateway = Mock()
    
    // Stub — we only care about return values
    PricingService pricingService = Stub()
    
    PaymentService service

    def setup() {
        pricingService.calculateTotal(_) >> new Price(amount: 99.99, currency: "USD")
        service = new PaymentService(gateway, pricingService)
    }

    def "processes payment and charges gateway"() {
        when:
        service.processOrder(orderId: "order_123", customerId: "cust_456")

        then:
        1 * gateway.charge(_, { it.amount == 99.99 })
    }
}

Stubbing Return Values

The >> operator configures return values for stubbed interactions:

UserRepository repository = Stub()

def setup() {
    // Always return this user for any argument
    repository.findById(_) >> new User(id: "u1", email: "test@example.com")
    
    // Return different values for different arguments
    repository.findByEmail("admin@example.com") >> new User(id: "u2", role: "admin")
    repository.findByEmail("user@example.com") >> new User(id: "u3", role: "user")
    repository.findByEmail(_) >> null // default for anything else
    
    // Return a list
    repository.findAll() >> [
        new User(id: "u1"),
        new User(id: "u2"),
    ]
    
    // Throw an exception
    repository.findById("deleted") >> { throw new NotFoundException("deleted") }
}

For sequential return values:

// First call returns true, second returns false, subsequent calls return false
service.isAvailable() >>> [true, false]

// Cycle through values repeatedly
service.nextValue() >> { [1, 2, 3].iterator() }

Interaction Constraints

In the then: block, Spock verifies interactions. The syntax is N * mock.method(args):

def "sends confirmation email after successful order"() {
    given:
    def order = new Order(customerId: "cust_123", items: [item1, item2])

    when:
    orderService.place(order)

    then:
    1 * emailService.sendConfirmation("cust_123", _)
    0 * emailService.sendFailureNotice(_)
    1 * inventoryService.reserve(_)
}

Cardinality expressions:

1 * mock.method(args)    // exactly once
2 * mock.method(args)    // exactly twice
0 * mock.method(args)    // never
_ * mock.method(args)    // any number of times (including zero)
(1..3) * mock.method()   // between 1 and 3 times
(1.._) * mock.method()   // at least once

Argument Matchers

Spock's argument matchers make interaction constraints flexible:

then:
// Wildcard — any argument
1 * gateway.charge(_)

// Type check
1 * gateway.charge(_ as PaymentRequest)

// Closure predicate — most flexible
1 * gateway.charge({ PaymentRequest req ->
    req.amount > 0 &&
    req.currency == "USD" &&
    req.customerId != null
})

// Hamcrest matchers work too
1 * gateway.charge({ it.amount == 99.99 })

// Exact value
1 * repository.findById("user_123")

For methods with multiple arguments, match each individually:

// First argument is any string, second is exactly 50
1 * service.paginate(_, 50)

// Both wildcard
1 * service.paginate(_, _)

// First is predicate, second is exact
1 * service.paginate({ it.length > 3 }, 10)

Combined Stubbing and Interaction Verification

A Mock() can both stub return values and verify interactions in the same spec. Use >> inside the then: block:

def "returns created order after gateway confirms"() {
    when:
    def result = service.processOrder("order_123")

    then:
    1 * gateway.charge(_) >> new ChargeResult(status: "success", transactionId: "txn_456")
    result.transactionId == "txn_456"
    result.status == OrderStatus.CONFIRMED
}

The interaction constraint (1 *) and the stub (>> new ChargeResult(...)) are combined on one line. Spock applies the stub and verifies the interaction count.

Interaction Ordering

By default, Spock doesn't enforce call order. To verify that method A is called before method B:

def "validates before charging"() {
    when:
    service.checkout(cart)

    then:
    1 * validator.validate(cart)

    then:
    1 * gateway.charge(_)
}

Multiple then: blocks enforce ordering — the first block's interactions must occur before the second block's. Within a single then: block, order is not enforced.

Spy: Partial Mocking

Spy() wraps a real object. Methods you don't configure call through to the real implementation. Methods you configure are intercepted:

class EmailServiceSpec extends Specification {

    EmailTemplateEngine templateEngine = new EmailTemplateEngine() // real object
    EmailService service = Spy(constructorArgs: [templateEngine])

    def "renders template and logs"() {
        given:
        // Override just the send method — real template rendering still happens
        service.send(_, _) >> { String to, String body -> 
            // custom behavior — don't actually send the email
            println "Would send to $to: $body"
        }

        when:
        service.sendWelcomeEmail("user@example.com", "Alice")

        then:
        // Verify the spy was called
        1 * service.send("user@example.com", _)
        // Verify the real template engine was invoked internally
        1 * service.renderTemplate("welcome", _)
    }
}

Spy is useful when:

  • The class has complex real logic you want to keep
  • You only need to stub external calls (HTTP, database) not internal ones
  • You're testing protected or package-private methods alongside public ones

Verifying No Other Interactions

After your expected interactions, verify nothing else was called:

def "only charges once, no double-charging"() {
    when:
    service.processPayment(orderId)

    then:
    1 * gateway.charge(_) >> successResult
    0 * gateway._  // no other gateway methods called
    _ * logger._   // ignore all logger calls
}

The 0 * mock._ pattern (wildcard on method) asserts that no other methods on that mock were called. Combine with _ * otherMock._ to ignore collaborators you're not testing.

Global Interaction Constraints

You can declare interactions in setup() that apply to all tests, then override or add to them in individual then: blocks:

class OrderServiceSpec extends Specification {

    OrderRepository repository = Mock()
    EventBus eventBus = Mock()
    OrderService service

    def setup() {
        service = new OrderService(repository, eventBus)
        
        // Default stub — all tests get this unless overridden
        repository.findById(_) >> Optional.empty()
    }

    def "publishes event after creating order"() {
        given:
        def request = new CreateOrderRequest(customerId: "cust_1")

        when:
        service.create(request)

        then:
        1 * repository.save(_ as Order)
        1 * eventBus.publish(_ as OrderCreatedEvent)
    }

    def "throws when order not found"() {
        when:
        service.getOrder("nonexistent")

        then:
        thrown(OrderNotFoundException)
        // repository.findById returns Optional.empty() from setup()
        0 * eventBus._
    }
}

Mocking with Generics

Spock handles generic types correctly. When mocking, the type parameter is erased at runtime, but you can use closure predicates to verify generic arguments:

Repository<User> userRepository = Mock()

then:
1 * userRepository.save({ User user -> user.email == "test@example.com" })

For generic return types:

userRepository.findAll() >> [new User(id: "u1"), new User(id: "u2")]

Common Mistakes

Using Stub() when you need to verify calls: Stubs don't fail on unexpected interactions. If you need to assert that a method was called, use Mock().

Putting stubbing in then: and forgetting it affects cardinality: 1 * mock.method() >> value counts as "called exactly once AND returns value". If the method is called zero times, the test fails.

Wildcard ordering issues: 0 * mock._ in a then: block must come after all expected interactions, or it will match them and fail. Order your interaction constraints from most specific to most general.

Spying on final classes: Spy() uses subclassing, which doesn't work on final classes in Java/Groovy. If you need to spy on a final class, you'll need to use Mockito's mockMaker-inline extension or restructure the code.

Forgetting that closures capture the value at call time: When using >> with a closure, the closure executes at call time, not setup time. Variables referenced inside the closure are evaluated when the stubbed method is called, not when the stub is configured.

Read more

Start now free