RSpec mocking
What is Mocking?
Mocking is a way to create fake objects or methods to stand in for real ones during testing, simulating the behavior of real components, allowing you to focus on testing one piece of code without depending on other parts.
Mocking in a Nutshell:
- Mocks are fake objects or methods that simulate behavior to isolate the code you’re testing.
- With practice, mocking will become second nature and make your tests faster, more reliable, and easier to write.
- Use allow and expect to define and verify mocked behavior.
Why Use Mocking?
- Isolate the Code Being Tested: Test your code without relying on external systems (e.g., databases, APIs).
- Speed Up Tests: Mocks are faster than real systems like APIs or databases. Imagine you’re testing a delivery service app that calculates delivery times. Instead of waiting for real GPS data (slow and unpredictable), you mock the GPS system to always return “10 minutes away.”
- Control the Test Environment: Simulate specific scenarios that might be hard to replicate otherwise (e.g., an API returning an error).
- Avoid Side Effects: Prevent unintended changes, like modifying a real database during testing.
How Mocking Works in RSpec
Example without Mocking:
1
2
3
4
5
6
7
8
9
10
11
12
13
class WeatherService
def fetch_weather(city)
# Imagine this makes a slow API call
"Sunny in #{city}"
end
end
class Greeting
def greet(city)
weather = WeatherService.new.fetch_weather(city)
"Hello! It's #{weather}."
end
end
If you test Greeting#greet, it depends on WeatherService, which takes time to fetch data and might fail if the API is down.
Example with Mocking:
You can mock the WeatherService so it doesn’t make a real API call:
1
2
3
4
5
6
7
8
9
10
11
RSpec.describe Greeting do
it "returns a greeting with mocked weather" do
weather_service = instance_double("WeatherService")
allow(weather_service).to receive(:fetch_weather).and_return("Sunny in Paris")
greeting = Greeting.new
allow_any_instance_of(WeatherService).to receive(:fetch_weather).and_return("Sunny in Paris")
expect(greeting.greet("Paris")).to eq("Hello! It's Sunny in Paris.")
end
end
Key Mocking Terms
- Mock: A fake object that can be programmed to “act” a certain way.
- Stub: A predefined return value for a method. Example: “If fetch_weather is called, always return ‘Sunny.’”
- Double: A generic term for any mocked object in RSpec (instance_double, class_double, etc.).
How to Mock in RSpec
1. allow
Use allow to tell a mock object how to behave:
allow(object).to receive(:method).and_return(value)
Example:
1
2
weather_service = instance_double("WeatherService")
allow(weather_service).to receive(:fetch_weather).and_return("Rainy")
2. expect with Mocks
You can verify if a method was called:
expect(object).to receive(:method).with(arguments)
Example:
1
2
3
weather_service = instance_double("WeatherService")
expect(weather_service).to receive(:fetch_weather).with("London")
weather_service.fetch_weather("London")
When to Use Mocks
- External Services: Replace slow or unreliable services (e.g., APIs, databases).
- Expensive Processes: Skip complex computations or processes during tests.
- Unwritten Code: Mock components that aren’t ready yet.
Tips for Mocking
- Don’t Over-Mock: Only mock what you absolutely need.
- Mock Behavior, Not State: Focus on what methods do rather than what objects are.
- Use instance_double: Helps ensure your mock matches the real class’s interface.
Send feedback!