rspec-mocks
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.
What this skill does
# 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
```ruby
# Anonymous double
user = double
# Named double (better error messages)
user = double("user")
# Double with stubs
user = double("user", name: "John", email: "[email protected]")
```
### Stubbing Methods
```ruby
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: "[email protected]")
```
### Return Values
```ruby
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.**
```ruby
# 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
```ruby
# 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:
```ruby
user = instance_double(User).as_null_object
user.anything # Returns nil instead of error
```
## Message Expectations
### expect vs allow
```ruby
# 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
```ruby
# Must be called
expect(mailer).to receive(:send_email)
# Must be called with specific arguments
expect(mailer).to receive(:send_email).with("[email protected]", "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
```ruby
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):
```ruby
# 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
```ruby
# 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
```ruby
# 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:
```ruby
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
```ruby
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
```ruby
# 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**
```ruby
# 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:
```ruby
expect(logger).to receive(:start).ordered
expect(processor).to receive(:process).ordered
expect(logger).to receive(:finish).ordered
```
## Configuration
```ruby
# 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
```ruby
# Good - catches interface changes
user = instance_double(User, name: "John")
# Avoid - doesn't verify interface
user = double("user", name: "John")
```
### Prefer Spies for Verification
```ruby
# 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
```ruby
# 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:
```ruby
# 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
- **`references/mock-pattRelated in Code Review
gstack
IncludedFast headless browser for QA testing and site dogfooding. Navigate pages, interact with elements, verify state, diff before/after, take annotated screenshots, test responsive layouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack)
startup-due-diligence
IncludedLegal due diligence review for seed-stage and Series A startups (US, Delaware C-Corp focus). Supports both investor and founder perspectives. Capabilities include: (1) Interactive document review and issue spotting; (2) Document request list generation; (3) Cap table and SAFE/convertible note analysis; (4) Red flag identification with severity ratings; (5) Diligence report generation. TRIGGERS: due diligence, DD, startup investment, cap table review, Series A, seed round, investor diligence, legal review startup, SAFE analysis, convertible note, 409A, founder vesting.
interview-master
IncludedThis skill should be used when the user asks to "generate interview questions", "prepare for interview", "optimize resume", "conduct mock interview", "analyze git commits for resume", "generate resume from code", "review my resume", or mentions interview preparation, career assistance, or extracting project experience from git history. Provides comprehensive interview and career development guidance for both job seekers and interviewers.
fix-issue
IncludedFixes GitHub issues using parallel analysis agents for root cause investigation, code exploration, and regression detection. Reads issue context from gh CLI, searches codebase and memory for related patterns, generates a fix with tests, and links the resolution back to the issue via PR. Includes prevention analysis to avoid recurrence. Use when debugging errors, resolving regressions, fixing bugs, or triaging issues.
sf-apex
IncludedGenerates and reviews Salesforce Apex code with 150-point scoring. TRIGGER when: user writes, reviews, or fixes Apex classes, triggers, test classes, batch/queueable/schedulable jobs, or touches .cls/.trigger files. DO NOT TRIGGER when: LWC JavaScript (use sf-lwc), Flow XML (use sf-flow), SOQL-only queries (use sf-soql), or non-Salesforce code.
swift-development
IncludedComprehensive Swift development for building, testing, and deploying iOS/macOS applications. Use when Claude needs to: (1) Build Swift packages or Xcode projects from command line, (2) Run tests with XCTest or Swift Testing framework, (3) Manage iOS simulators with simctl, (4) Handle code signing, provisioning profiles, and app distribution, (5) Format or lint Swift code with SwiftFormat/SwiftLint, (6) Work with Swift Package Manager (SPM), (7) Implement Swift 6 concurrency patterns (async/await, actors, Sendable), (8) Create SwiftUI views with MVVM architecture, (9) Set up Core Data or SwiftData persistence, or any other Swift/iOS/macOS development tasks.