← Back to list

rspec-mocks
by bastos
Bastos' Claude Code Ruby Plugin Marketplace
⭐ 0🍴 0📅 Jan 24, 2026
SKILL.md
name: RSpec Mocks description: 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. version: 1.0.0
RSpec Mocks
RSpec Mocks provides test doubles for isolating code under test from external dependencies.
Test Double Types
| 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 |
Basic Doubles
Creating Doubles
# Anonymous double
user = double
# Named double (better error messages)
user = double("user")
# Double with stubs
user = double("user", name: "John", email: "john@example.com")
Stubbing Methods
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")
Return Values
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
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")
Verifying Double Benefits
# 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
Null Object Doubles
Return nil for unstubbed methods instead of raising:
user = instance_double(User).as_null_object
user.anything # Returns nil instead of error
Message Expectations
expect vs allow
# 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)
Verifying Calls
# 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)
Argument Matchers
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
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)
Spy vs Mock Style
# 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)
Spy Helpers
# 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)
Partial Doubles
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)
Class Method Stubbing
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")
Dangerous: Stubbing Any Instance
# 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)
Ordering
Enforce call order:
expect(logger).to receive(:start).ordered
expect(processor).to receive(:process).ordered
expect(logger).to receive(:finish).ordered
Configuration
# 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
Best Practices
Use Verifying Doubles
# Good - catches interface changes
user = instance_double(User, name: "John")
# Avoid - doesn't verify interface
user = double("user", name: "John")
Prefer Spies for Verification
# 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
Don't Over-Mock
# 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 at Boundaries
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?
Additional Resources
Reference Files
references/mock-patterns.md- Common mocking patternsreferences/test-isolation.md- When and what to mock
Example Files
examples/service_with_mocks.rb- Service testing with mocksexamples/api_client_spec.rb- Mocking HTTP clients
Score
Total Score
65/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon
