In this lesson, you will learn how to choose the correct test double to isolate your unit tests, ensuring you are testing the right thing at the right time.
When unit testing, we often replace real dependencies with "test doubles." Choosing the wrong one leads to brittle tests that break during refactoring or fail to detect actual regressions. We distinguish them based on how they facilitate your test.
The Three Categories
1. Stubs: For State Verification
A Stub is a minimal implementation that provides indirect input to your code under test. You use a stub when you don't care how your code calls a dependency, only that the dependency provides the data necessary for the code to perform its job.
- Goal: Feed the method.
- Example: A stubbed
IUserRepositorythat always returns a hardcodedUserobject so you can test yourDiscountCalculatorlogic.
2. Mocks: For Interaction Verification
A Mock is an object programmed with expectations. You use a mock when the behavior—the specific interaction—is the primary requirement. If your code must call an EmailService exactly once with specific arguments, you use a mock.
- Goal: Verify the contract/protocol.
- Example: Using
Moqto verify that_emailService.Verify(x => x.Send("admin@test.com"), Times.Once()).
3. Fakes: For Functional Realism
A Fake is a working implementation, but one that is simplified and unsuitable for production. An in-memory database or a file system wrapper that stores data in a Dictionary are classic fakes.
- Goal: Represent a simplified operational dependency.
- Example: A
FakeDatabaseclass that stores objects in aList<T>instead of calling SQL, allowing for faster integration testing without external overhead.
Choosing the Right Tool
The decision hinges on your testing goal:
- If you care about the output (State): Use a Stub or a Fake. You are validating the "what."
- If you care about the process (Behavior): Use a Mock. You are validating the "how."
Over-mocking creates tests that are tightly coupled to the implementation details of your code. If you find yourself mocking every single internal method call, you are likely testing the implementation rather than the observable behavior. Prefer Stubs for data-heavy tests and reserve Mocks for critical interactions where the side effect (like sending an email or charging a card) is the primary outcome.
Key Takeaway: Use Stubs/Fakes to provide necessary data for state verification, and use Mocks only when you must strictly enforce and verify the internal behavioral protocol of your dependencies.