Why Your Tests Are Lying, and How to Fix It?
\n\nImagine: you write perfect C# code, cover it with tests, the green checkmark lights up, the CI pipeline is happy. You ship the release… and production crashes. Sound familiar? Most likely, the problem isn't in the code, but in the tests. They weren't testing your real code, but a "toy" version tied to a database, file system, or external API.
\n\nWelcome to the world of mock objects and fixtures — two pillars that support high-quality unit testing in C#. Without them, your tests are fragile integration checks that break at the slightest change in the environment. With them, you get fast, isolated, and reliable tests that truly protect your code.
\n\nIn this article, we'll break down: how mock differs from stub and fake, how to properly configure fixtures, and show live examples with popular libraries: Moq, NSubstitute, and AutoFixture. Let's go!
\n\nMock, Stub, Fake — What's the Difference? (Spoiler: It Matters)
\n\nBefore writing code, let's agree on terminology. Many developers use the word "mock" for everything, but this leads to confusion. In Gerard Meszaros's classification (book "xUnit Test Patterns"), there is a clear distinction:
\n\n- \n
- Dummy — an object that is passed but never used. Needed only to fill parameters. \n
- Stub — provides pre-prepared answers to calls. You check state (state verification). \n
- Fake — a working but simplified implementation (e.g., InMemoryRepository instead of a real database). \n
- Mock — an object that checks behavior (behavior verification). You assert that a specific method was called with specific arguments. \n
In practice, in C# we most often use mock objects (via Moq or NSubstitute) to verify interactions, and fixtures (via AutoFixture) for quick test data generation. But let's take it step by step.
\n\nMock Objects in C#: Isolating Dependencies
\n\nImagine you have an OrderService that, when placing an order, sends an email and saves data to the database. Testing it "for real" means sending emails and writing to the database every time. This is slow, unreliable, and expensive.
The solution is dependency isolation. We replace real objects (EmailSender, DbContext) with mock objects and verify that the service calls the required methods with the correct parameters.
\n\nExample with Moq (the most popular library)
\n\npublic interface IEmailSender\n{\n void Send(string to, string subject, string body);\n}\n\npublic interface IOrderRepository\n{\n void Save(Order order);\n}\n\npublic class OrderService\n{\n private readonly IEmailSender _emailSender;\n private readonly IOrderRepository _repository;\n\n public OrderService(IEmailSender emailSender, IOrderRepository repository)\n {\n _emailSender = emailSender;\n _repository = repository;\n }\n\n public void PlaceOrder(Order order)\n {\n // Business logic\n _repository.Save(order);\n _emailSender.Send(order.CustomerEmail, "Order Confirmed", $"Your order #{order.Id} has been placed.");\n }\n}\n\nNow let's write a test using Moq:
\n\n[TestFixture]\npublic class OrderServiceTests\n{\n [Test]\n public void PlaceOrder_ShouldSendEmail_WhenOrderIsValid()\n {\n // Arrange\n var mockEmailSender = new Mock<IEmailSender>();\n var mockRepository = new Mock<IOrderRepository>();\n var service = new OrderService(mockEmailSender.Object, mockRepository.Object);\n var order = new Order { Id =