0
0
Rubyprogramming~15 mins

Mocking and stubbing in Ruby - Deep Dive

Choose your learning style9 modes available
Overview - Mocking and stubbing
What is it?
Mocking and stubbing are techniques used in testing to replace parts of a program with fake versions. These fake parts simulate real behavior so you can test how your code works without relying on external systems or complex dependencies. Mocking usually checks if certain actions happen, while stubbing provides preset responses to method calls. Together, they help isolate the code under test and make tests faster and more reliable.
Why it matters
Without mocking and stubbing, tests would depend on real databases, web services, or slow operations, making them fragile and slow. This would cause developers to avoid testing or get false failures. Mocking and stubbing let you focus on the logic you want to verify, improving confidence and speed. They also help catch bugs early by controlling the environment and inputs precisely.
Where it fits
Before learning mocking and stubbing, you should understand basic Ruby syntax, writing methods, and how to write simple tests using frameworks like Minitest or RSpec. After mastering mocking and stubbing, you can explore advanced testing topics like test doubles, spies, integration testing, and behavior-driven development (BDD).
Mental Model
Core Idea
Mocking and stubbing replace real parts of a program with controlled fake versions to test code behavior in isolation.
Think of it like...
It's like using a flight simulator instead of a real airplane to practice flying. The simulator mimics the plane's controls and responses so you can learn safely without risks or costs.
┌───────────────┐       ┌───────────────┐
│   Your Code   │──────▶│ Mock / Stub   │
└───────────────┘       └───────────────┘
         │                      │
         │ Calls methods        │ Returns preset or checks calls
         ▼                      ▼
┌───────────────┐       ┌───────────────┐
│ Real Services │       │ Controlled    │
│ (DB, APIs)    │       │ Fake Behavior │
└───────────────┘       └───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding test doubles basics
🤔
Concept: Introduce the idea of test doubles as stand-ins for real objects in tests.
In testing, a test double is any object that replaces a real one to control behavior or observe interactions. The simplest form is a stub, which returns fixed values. For example, if your code calls a method to get user data, you can stub that method to return a fake user instead of querying a database.
Result
You can run tests without needing the real user data source, making tests faster and more predictable.
Understanding test doubles is the foundation for isolating code and making tests reliable by controlling dependencies.
2
FoundationDifference between mocking and stubbing
🤔
Concept: Clarify the distinct roles of mocks and stubs in testing.
Stubs provide canned responses to method calls, ignoring how many times or if the method was called. Mocks, on the other hand, set expectations about how methods should be called and can fail tests if those expectations are not met. For example, a stub might always return '42' when asked, while a mock might expect the method to be called exactly once with specific arguments.
Result
You learn when to use stubs to fake data and when to use mocks to verify interactions.
Knowing this difference helps you write clearer tests that either focus on output or on behavior.
3
IntermediateCreating stubs in Ruby with RSpec
🤔Before reading on: do you think stubbing a method changes the original method permanently or only during the test? Commit to your answer.
Concept: Learn how to replace method responses temporarily using RSpec's stubbing syntax.
In RSpec, you can stub a method on an object using `allow(object).to receive(:method).and_return(value)`. This means when the method is called during the test, it returns the value you specify instead of running the original code. For example: user = double('User') allow(user).to receive(:name).and_return('Alice') Now, calling `user.name` returns 'Alice' without any real user object.
Result
Tests run faster and don't depend on real data or complex logic inside the stubbed method.
Knowing that stubs only affect the method during the test prevents confusion about side effects and keeps tests isolated.
4
IntermediateUsing mocks to verify method calls
🤔Before reading on: do you think mocks only check if a method was called or also what arguments it was called with? Commit to your answer.
Concept: Learn how to set expectations on method calls using mocks in RSpec.
Mocks let you expect that a method is called with certain arguments. In RSpec, you can write: expect(object).to receive(:method).with(args) This test will fail if the method is not called exactly as expected. For example: notifier = double('Notifier') expect(notifier).to receive(:send_email).with('hello@example.com') Your code must call `send_email('hello@example.com')` on `notifier` or the test fails.
Result
You can verify that your code interacts correctly with other parts, not just produce the right output.
Understanding how to check interactions helps catch bugs where the code forgets to call important methods or calls them incorrectly.
5
IntermediateCombining mocks and stubs in tests
🤔
Concept: Learn how to use both mocks and stubs together to fully control and verify behavior.
Often, you stub some methods to provide data and mock others to check calls. For example, you might stub a database query to return fake data and mock a logger to check if a message was logged: allow(db).to receive(:find).and_return(fake_record) expect(logger).to receive(:info).with('Record found') This combination lets you test complex scenarios without real dependencies.
Result
Tests become more expressive and focused on what matters for the feature.
Knowing when to stub and when to mock in the same test improves test clarity and effectiveness.
6
AdvancedAvoiding over-mocking pitfalls
🤔Before reading on: do you think mocking every method call in a test is a good practice or can it cause problems? Commit to your answer.
Concept: Understand the risks of excessive mocking and how to balance it.
Over-mocking can make tests fragile because they depend on internal implementation details. If you mock too many methods, small code changes break tests even if behavior is correct. Instead, mock only external dependencies or interactions that matter. Use real objects for simple data structures or logic that doesn't need isolation.
Result
Tests become more maintainable and less brittle over time.
Knowing the limits of mocking prevents wasted time fixing tests broken by harmless refactors.
7
ExpertHow Ruby handles mocks and stubs internally
🤔Before reading on: do you think Ruby changes the original method permanently when mocking or stubbing, or does it use a temporary override? Commit to your answer.
Concept: Explore Ruby's method replacement mechanism during mocking and stubbing.
Ruby uses a feature called 'method redefinition' to replace methods at runtime. When you stub or mock a method, Ruby saves the original method and replaces it with a new one that returns the fake response or checks calls. After the test, Ruby restores the original method to avoid side effects. This is done using Ruby's `Module#alias_method` or `define_method` internally by testing libraries.
Result
You understand why mocks and stubs only affect tests temporarily and how Ruby supports this dynamic behavior.
Knowing Ruby's internal method swapping explains why mocks and stubs are safe and how to debug tricky test issues.
Under the Hood
Mocking and stubbing work by dynamically replacing methods on objects or classes at runtime. Ruby's flexible object model allows methods to be redefined temporarily. When a stub or mock is set, the original method is saved, and a new method is defined that either returns preset values (stub) or records calls and checks expectations (mock). After the test finishes, the original method is restored to keep the program unchanged outside tests.
Why designed this way?
Ruby was designed to be highly dynamic and flexible, allowing methods to be changed on the fly. This makes mocking and stubbing natural and easy to implement without special language features. The design trades off some performance for great testing flexibility and developer productivity. Alternatives like static languages require more complex frameworks or proxies.
┌───────────────┐
│ Original Code │
│  with method  │
└──────┬────────┘
       │ Save original method
       ▼
┌───────────────┐
│ Mock/Stub     │
│ replaces      │
│ method at     │
│ runtime       │
└──────┬────────┘
       │ Calls go to mock/stub
       ▼
┌───────────────┐
│ Fake behavior │
│ (return value │
│ or check call)│
└──────┬────────┘
       │ After test
       ▼
┌───────────────┐
│ Restore       │
│ original      │
│ method        │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does stubbing a method permanently change it for all code? Commit to yes or no.
Common Belief:Stubbing a method changes it forever, affecting all future code.
Tap to reveal reality
Reality:Stubbing only replaces the method temporarily during the test and restores the original afterward.
Why it matters:Believing stubs are permanent can cause confusion and fear of side effects, preventing effective testing.
Quick: Do mocks only check if a method was called, or do they also check arguments? Commit to your answer.
Common Belief:Mocks only check if a method was called, not what arguments were used.
Tap to reveal reality
Reality:Mocks can check both if a method was called and with what arguments, making them powerful for verifying interactions.
Why it matters:Underestimating mocks limits test coverage and misses bugs related to incorrect method usage.
Quick: Is it good to mock every method your code calls in tests? Commit to yes or no.
Common Belief:Mocking every method call makes tests more reliable and better.
Tap to reveal reality
Reality:Over-mocking makes tests fragile and tightly coupled to implementation details, causing frequent breaks on refactor.
Why it matters:Excessive mocking wastes time fixing tests and reduces confidence in test results.
Quick: Can you use mocks and stubs interchangeably without changing test meaning? Commit to yes or no.
Common Belief:Mocks and stubs are the same and can be swapped freely.
Tap to reveal reality
Reality:Mocks verify behavior and expectations, while stubs only provide data; swapping them changes test intent and results.
Why it matters:Confusing mocks and stubs leads to unclear tests and missed bugs.
Expert Zone
1
Mocks can be partial, allowing some real methods to run while mocking others, which helps test complex objects without full replacement.
2
Stubbing class methods vs instance methods requires different approaches and understanding Ruby's object model deeply to avoid leaks between tests.
3
RSpec's verifying doubles check that mocked methods actually exist on real objects, preventing tests from passing with invalid method names.
When NOT to use
Avoid mocking or stubbing when testing integration or end-to-end behavior where real interactions matter. Instead, use real objects or test doubles that simulate full behavior. For performance issues, consider database cleaning or test environment optimization rather than over-mocking.
Production Patterns
In production codebases, mocking and stubbing are used to isolate units in service objects, controllers, or models. Teams often use verifying doubles to catch interface mismatches early. Mocks are used to test event-driven code by verifying message sends, while stubs fake external API responses to avoid network calls.
Connections
Dependency Injection
Builds-on
Mocking and stubbing rely on dependency injection to replace real dependencies with test doubles, making code more testable and modular.
Simulation in Engineering
Same pattern
Just like mocking simulates parts of software, engineers use simulations to test systems safely and cheaply before real-world deployment.
Placebo Effect in Medicine
Analogy in control
Mocking controls variables in tests like placebos control expectations in clinical trials, isolating the effect of the treatment or code.
Common Pitfalls
#1Mocking too many internal methods causing fragile tests
Wrong approach:expect(user).to receive(:calculate_age) expect(user).to receive(:format_name) expect(user).to receive(:send_notification)
Correct approach:expect(user).to receive(:send_notification)
Root cause:Misunderstanding that tests should verify behavior, not internal implementation details.
#2Stubbing methods globally causing side effects in other tests
Wrong approach:allow(User).to receive(:find).and_return(fake_user) # without cleanup
Correct approach:allow(User).to receive(:find).and_return(fake_user) # inside example or with proper teardown
Root cause:Not scoping stubs to individual tests leads to persistent changes affecting unrelated tests.
#3Confusing mocks and stubs and using mocks when only data is needed
Wrong approach:expect(user).to receive(:name).and_return('Alice') # expecting call but not verifying it
Correct approach:allow(user).to receive(:name).and_return('Alice') # stub when only data is needed
Root cause:Not distinguishing between verifying behavior and providing data causes unclear tests.
Key Takeaways
Mocking and stubbing let you replace parts of your program with controlled fakes to isolate and speed up tests.
Stubs provide preset responses, while mocks verify that methods are called correctly with expected arguments.
Ruby's dynamic method replacement makes mocking and stubbing safe and temporary during tests.
Overusing mocks can make tests fragile; use them wisely to focus on important interactions.
Understanding mocking and stubbing deeply improves test quality, maintainability, and developer confidence.