Mocking and Test Doubles
Test doubles replace slow or unpredictable dependencies — databases, payment gateways, clocks — so unit tests stay fast and focused.
On this page · 5 min · Intermediate
On this page
- Learning objectives
- Explanation
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Intermediate
- Updated
- Oct 29, 2026
Learning objectives#
- Name the types of test doubles
- Stub return values
- Verify interactions with mocks
- Avoid over-mocking
Explanation#
Dummy: passed but never used. Stub: returns canned answers. Fake: simple working implementation (in-memory repo). Mock: records and verifies calls. Spy: wraps a real object and records calls.
C#: Moq or NSubstitute. JavaScript: jest.fn(), jest.mock().
Mock at boundaries you own (interfaces to email, payment, time). Over-mocking internal details makes tests fragile and less meaningful.
Code example: Stubbing and verifying with Moq#
// dotnet add package Moq
public interface IPaymentGateway { Task<bool> ChargeAsync(decimal amount); }
public interface IEmailSender { Task SendAsync(string to, string subject); }
public class CheckoutService(IPaymentGateway pay, IEmailSender email)
{
public async Task<bool> PlaceOrderAsync(string customer, decimal total)
{
if (!await pay.ChargeAsync(total)) return false;
await email.SendAsync(customer, "Order confirmed");
return true;
}
}
public class CheckoutServiceTests
{
[Fact]
public async Task Successful_payment_sends_confirmation()
{
var pay = new Mock<IPaymentGateway>();
pay.Setup(p => p.ChargeAsync(500m)).ReturnsAsync(true); // stub
var email = new Mock<IEmailSender>();
var ok = await new CheckoutService(pay.Object, email.Object).PlaceOrderAsync("asha@example.com", 500m);
Assert.True(ok);
email.Verify(e => e.SendAsync("asha@example.com", "Order confirmed"), Times.Once); // mock
}
}
Passed — no real payment or email was sent, and the test proves the email is sent exactly once.
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- The payment gateway is stubbed to succeed.
- The email sender is a mock.
- The service runs with both doubles.
- Verify checks the interaction happened once.
Real-world use case#
Checkout logic is tested in milliseconds without calling the real payment provider.
Common mistakes#
Tests then only check that code calls code. Use fakes or real objects for simple collaborators.
Wrap third-party SDKs behind your own interface and mock that.
Best practices#
- Mock at system boundaries
- Prefer stubs over strict mocks
- Use fakes for repositories
- Verify only important interactions
Remember this#
Stub = canned answer. Mock = verifies calls.
Mock boundaries, not internals.
Wrap third-party SDKs.
Practice exercise#
Your task: Stub ChargeAsync to fail and assert PlaceOrderAsync returns false and sends no email.
Show solution
pay.Setup(p => p.ChargeAsync(It.IsAny<decimal>())).ReturnsAsync(false);
// ...
Assert.False(ok);
email.Verify(e => e.SendAsync(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
Mini quiz#
Which test double verifies that a method was called?
Want more? Practise all Automation Testing MCQs · Automation Testing interview questions
My notes#
Saved in this browser.