Help us improve
Share bugs, ideas, or general feedback.
From rspec
This skill should be used when the user asks about "test doubles", "mocking", "stubbing", "spies", "verifying doubles", "partial doubles", "allow", "receive", "have_received", or needs guidance on isolating tests and mocking dependencies in RSpec.
npx claudepluginhub bastos/ruby-plugin-marketplace --plugin rspecHow this skill is triggered — by the user, by Claude, or both
Slash command
/rspec:rspec-mocksThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
RSpec Mocks provides test doubles for isolating code under test from external dependencies.
Implements RSpec mocking with doubles, stubs, mocks, spies, and partial strategies for Ruby test suites.
Guides RSpec testing for Ruby and Rails apps covering model specs, request specs, system specs, factories, mocks, and TDD workflow. Triggers on RSpec keywords and testing scenarios.
Writes RSpec tests for Ruby and Rails apps using AAA pattern, describe/context blocks, subject/let, fixtures, allow/expect mocking, and shoulda matchers. Use for spec files, test cases, or new features.
Share bugs, ideas, or general feedback.
RSpec Mocks provides test doubles for isolating code under test from external dependencies.
| Type | Purpose |
|---|---|
| Double | Pure test object with no connection to real class |
| Verifying Double | Double that validates against real class interface |
| Partial Double | Real object with some methods stubbed |
| Spy | Records method calls for later verification |
# Anonymous double
user = double
# Named double (better error messages)
user = double("user")
# Double with stubs
user = double("user", name: "John", email: "john@example.com")
user = double("user")
allow(user).to receive(:name).and_return("John")
allow(user).to receive(:save).and_return(true)
# Multiple stubs at once
allow(user).to receive_messages(name: "John", email: "john@example.com")
allow(service).to receive(:call).and_return("result")
# Return different values on consecutive calls
allow(service).to receive(:call).and_return(1, 2, 3)
# First call returns 1, second returns 2, third+ returns 3
# Return value from block
allow(service).to receive(:call) { |arg| arg.upcase }
# Raise error
allow(service).to receive(:call).and_raise(StandardError, "error message")
allow(service).to receive(:call).and_raise(CustomError.new("message"))
# Throw symbol
allow(service).to receive(:call).and_throw(:abort)
# Yield to block
allow(service).to receive(:call).and_yield("value")
allow(service).to receive(:call).and_yield(1).and_yield(2)
# Call original implementation (partial doubles)
allow(service).to receive(:call).and_call_original
Verifying doubles check that stubbed methods exist on the real class. Always prefer verifying doubles over plain doubles.
# instance_double - verifies against instance methods
user = instance_double(User)
allow(user).to receive(:name).and_return("John")
allow(user).to receive(:nonexistent) # Raises error!
# class_double - verifies against class methods
UserService = class_double(UserService)
allow(UserService).to receive(:find).and_return(user)
# object_double - verifies against specific object
original_user = User.new
user = object_double(original_user, name: "John")
# If User class changes and removes `name` method:
# - Plain double: Tests pass but code is broken
# - Verifying double: Tests fail, alerting to the issue
user = instance_double(User)
allow(user).to receive(:fullname) # Typo! Raises:
# User does not implement #fullname
Return nil for unstubbed methods instead of raising:
user = instance_double(User).as_null_object
user.anything # Returns nil instead of error
# allow - Stub without requiring call (test setup)
allow(service).to receive(:call)
# expect - Must be called or test fails (behavior verification)
expect(service).to receive(:call)
# Must be called
expect(mailer).to receive(:send_email)
# Must be called with specific arguments
expect(mailer).to receive(:send_email).with("user@example.com", "Welcome!")
# Must be called specific number of times
expect(mailer).to receive(:send_email).once
expect(mailer).to receive(:send_email).twice
expect(mailer).to receive(:send_email).exactly(3).times
expect(mailer).to receive(:send_email).at_least(:once)
expect(mailer).to receive(:send_email).at_most(5).times
# Must not be called
expect(mailer).not_to receive(:send_spam)
expect(service).to receive(:call).with("exact value")
expect(service).to receive(:call).with(anything)
expect(service).to receive(:call).with(any_args)
expect(service).to receive(:call).with(no_args)
# Type matching
expect(service).to receive(:call).with(instance_of(User))
expect(service).to receive(:call).with(kind_of(Numeric))
# Pattern matching
expect(service).to receive(:call).with(/pattern/)
expect(service).to receive(:call).with(hash_including(key: "value"))
expect(service).to receive(:call).with(array_including(1, 2))
# Custom matching
expect(service).to receive(:call).with(satisfy { |arg| arg.valid? })
# Combining matchers
expect(service).to receive(:process).with(
instance_of(User),
hash_including(notify: true)
)
Spies verify calls after they happen (more natural test flow):
# Setup: allow the call
mailer = instance_double(Mailer)
allow(mailer).to receive(:send_email)
# Exercise: run the code
user_service = UserService.new(mailer)
user_service.register(user)
# Verify: check it was called
expect(mailer).to have_received(:send_email).with(user.email)
# Mock style (expect before action)
expect(mailer).to receive(:send_email)
user_service.register(user)
# Spy style (verify after action) - often clearer
allow(mailer).to receive(:send_email)
user_service.register(user)
expect(mailer).to have_received(:send_email)
# Create a spy that tracks all calls
user = spy("user")
user.name
user.email
user.save
expect(user).to have_received(:name)
expect(user).to have_received(:save)
# Verifying spy
user = instance_spy(User)
Stub methods on real objects:
user = User.new(name: "John")
allow(user).to receive(:premium?).and_return(true)
user.name # Returns "John" (real method)
user.premium? # Returns true (stubbed)
allow(User).to receive(:find).and_return(user)
allow(Time).to receive(:now).and_return(frozen_time)
allow(ENV).to receive(:[]).with("API_KEY").and_return("test-key")
# Avoid when possible - makes tests brittle
allow_any_instance_of(User).to receive(:premium?).and_return(true)
expect_any_instance_of(User).to receive(:save)
Better alternative: Dependency injection
# Instead of stubbing any instance
class UserService
def initialize(user_class: User)
@user_class = user_class
end
def create(attrs)
@user_class.new(attrs)
end
end
# Test with injected double
user_class = class_double(User)
service = UserService.new(user_class: user_class)
Enforce call order:
expect(logger).to receive(:start).ordered
expect(processor).to receive(:process).ordered
expect(logger).to receive(:finish).ordered
# spec/spec_helper.rb
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
# Verify partial doubles against real methods
mocks.verify_partial_doubles = true
# Verify doubles in before/after hooks
mocks.verify_doubled_constant_names = true
end
end
# Good - catches interface changes
user = instance_double(User, name: "John")
# Avoid - doesn't verify interface
user = double("user", name: "John")
# Good - arrange, act, assert order
allow(mailer).to receive(:send)
service.process
expect(mailer).to have_received(:send)
# Harder to read - expect before action
expect(mailer).to receive(:send)
service.process
# Too much mocking - testing implementation
allow(user).to receive(:first_name).and_return("John")
allow(user).to receive(:last_name).and_return("Doe")
expect(user.full_name).to eq("John Doe") # Just testing string concat
# Better - test real behavior
user = build(:user, first_name: "John", last_name: "Doe")
expect(user.full_name).to eq("John Doe")
Mock external services, not internal collaborators:
# Good - mocking external HTTP
allow(HTTPClient).to receive(:get).and_return(response)
# Questionable - mocking internal service
allow(UserValidator).to receive(:validate) # Maybe just use real one?
references/mock-patterns.md - Common mocking patternsreferences/test-isolation.md - When and what to mockexamples/service_with_mocks.rb - Service testing with mocksexamples/api_client_spec.rb - Mocking HTTP clients