Generative AI can be a blessing for junior developers, as long as they understand that the answers these tools provide should be a starting point — a springboard for diving deeper into a new topic, framework, or design decision.
However, I often see the opposite, especially when it comes to unit testing and test-driven development (TDD). Many junior developers today rely on last-driven development, powered by ChatGPT. While leveraging AI isn't inherently bad — after all, productivity is key — problems arise when developers copy-paste code without truly understanding it. This lack of comprehension makes it hard to discuss the "why" behind their code with their team or debug effectively when things go wrong.
That's why I decided to start this series. If you're doing unit testing and you think everything is a mock, then you're in the right place! I'll explain the differences between test doubles and guide you on a journey from a more "purist" testing strategy to practical tools like Moq, AutoFixture, and AutoMoq.
What are Test Doubles?
Test Double is a generic term coined by Gerard Meszaros for any object that replaces a real implementation during testing.
Think of a Test Double like a stunt double in a movie: it "stands in" for the real object during unit testing when the real object is unavailable, doesn't exist yet, is too complex, or is undesirable to use in a test scenario. The primary goal of a Test Double is to help you isolate the unit of code you're testing.
The 5 Types of Test Doubles
There are five types of Test Doubles, and we will explore them with simple and practical examples in the upcoming articles. For now, let’s define them one by one.
Dummy
A Dummy is the simplest Test Double. It does nothing—it’s only passed around to satisfy a method or constructor’s parameters but is never actually used.
Example: You might pass null or an empty object to a constructor when the parameter is not relevant to the test.
Stub
A Stub provides fixed and predictable responses to method calls. This is useful when you want the test to produce consistent and controlled outcomes.
Example: Returning a predefined value from a database query or a service call.
Spy
A Spy is like a Stub but with the added ability to record information about how methods are called—such as which parameters are passed and how many times the method was invoked. This makes it useful for assertions and verifications at the end of the test.
Example: Assert that a method was called a specific number of times with the expected parameters.
Mock
A Mock takes the concept of a Spy one step further: it can be pre-programmed with expectations about method calls, such as which parameters should be passed and how many times the methods should be invoked. If these expectations are not met, the Mock can throw an exception. Mocks usually include a Verify method to assert that the expectations were satisfied.
Example: Using a mocking framework like Moq to ensure a service method was called with specific inputs.
Fake
A Fake is a simplified but working implementation of a real object. Unlike Mocks or Stubs, Fakes actually contain some logic, but they often include shortcuts or less complex behavior compared to the real object. A common example is an in-memory database that replaces a real database for testing purposes.
Example: An in-memory repository that stores data in a list instead of connecting to an actual database.
In the next articles, we'll explore the different types of Test Doubles with practical C# examples—starting from a "purist" approach and progressing to the use of libraries such as Moq and AutoFixture.
Top comments (0)