Mocking Frameworks
Reviewed & published by Brayan K
By the end of this lesson you'll be able to isolate the class you're testing from its dependencies — databases, email, web APIs — by replacing them with test doubles. You'll hand-roll your own fakes, then use Moq to do it in two lines, and you'll know the difference between testing state and testing interactions — plus when not to mock at all.
Part of the free C# course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Name the five test doubles: dummy, stub, fake, mock, and spy
- Hand-roll a fake that records how its methods were called
- Inject dependencies so they can be swapped for doubles in a test
- Use Moq: new Mock, Setup/Returns, and Verify with Times
- Tell state testing (check the result) from interaction testing (check the calls)
- Recognise when NOT to mock — and avoid brittle, over-mocked tests
💡 Real-World Analogy
Car makers test safety with a crash-test dummy, not a real person. The dummy stands in for a human: it's the right shape and weight to make the test realistic, but it's controlled and disposable, so you can run the crash a hundred times without anyone getting hurt. A test double is exactly that — a stand-in for a real dependency. Your real email service sends actual emails and your real database is slow and shared; in a test you swap them for a controlled stand-in that you can drive, inspect, and throw away. The dummy isn't the point of the test — the car's behaviour is. Likewise, the double isn't what you're testing; it's there so you can test your own code in isolation.
The Test Double Taxonomy
"Mock" gets used as a catch-all, but there are really five kinds of test double (the names come from Gerard Meszaros via Martin Fowler). They differ in how much they do and what you check. You won't always say the word out loud, but knowing which one you're building keeps your tests honest.
| Double | What it does | You use it to… |
|---|---|---|
| dummy | Nothing — just fills a parameter | Satisfy a signature you don't actually call |
| stub | Returns canned, fixed answers | Make the code under test deterministic |
| fake | A working but lightweight implementation | Replace a real DB with an in-memory one |
| mock | Pre-programmed with expectations | Assert the right calls were made (interaction) |
| spy | Records how it was called | Inspect call counts and arguments afterwards |
The everyday split: a stub feeds data in (you assert on the result — state testing); a mock/spy records calls going out (you assert on the calls — interaction testing). A fake is a real, simplified implementation you could almost ship. Libraries like Moq blur the lines — one Mock<T> can act as a stub and a mock.
1. Hand-Rolling a Fake That Records Calls
Before any framework, understand what a double actually is — just a class that implements the same interface as the real thing. The key trick that makes it swappable is dependency injection: the class under test takes its dependency through its constructor rather than creating it, so in a test you hand it a double instead. This first double is a spy — it records how many times Send was called so the test can check the interaction. Read it, run it, then you'll build one.
using System;
using System.Collections.Generic;
// The dependency we want to keep OUT of our test — in real life this would
// actually send email over the network. We never want a unit test to do that.
interface IEmailService
{
void Send(string to, string subject);
}
// A hand-rolled FAKE (a "spy"): it implements the interface but instead of
// sending anything, it just RECORDS what it was asked to do. No framework needed.
class FakeEmailService : IEmailService
{
public int SendCount = 0; // how many times Send ran
public List<string> Recipients = new List<string>();
public void Send(string to, string subject)
{
SendCount++; // record the interaction
Recipients.Add(to); // remember who we "emailed"
}
}
// The class under test depends on IEmailService, not a concrete email sender.
// That's the key: it accepts the dependency through its constructor (injection).
class SignupService
{
private readonly IEmailService _email;
public SignupService(IEmailService email) => _email = email;
public void Register(string name)
{
// ...imagine saving the user here...
_email.Send(name + "@example.com", "Welcome!"); // the interaction we test
}
}
class Program
{
static void Main()
{
// Arrange — inject the FAKE instead of a real email service.
var fakeEmail = new FakeEmailService();
var service = new SignupService(fakeEmail);
// Act — run the code under test.
service.Register("alice");
service.Register("bob");
// Assert — interrogate the fake to see HOW it was used (interaction testing).
Console.WriteLine($"Send called {fakeEmail.SendCount} time(s)"); // Send called 2 time(s)
Console.WriteLine($"First recipient: {fakeEmail.Recipients[0]}"); // First recipient: [email protected]
bool pass = fakeEmail.SendCount == 2;
Console.WriteLine(pass ? "PASS ✅" : "FAIL ❌"); // PASS ✅
}
}
// ✅ Expected output:
// Send called 2 time(s)
// First recipient: [email protected]
// PASS ✅Your turn. The fake below is missing the bits that make it record anything. Fill in the three ___ blanks so the test can count the emails, then run it.
using System;
// 🎯 YOUR TURN — finish the FAKE so the test can count Send calls.
interface IEmailService
{
void Send(string to, string subject);
}
class FakeEmailService : IEmailService
{
// 1) Add a counter the test can read later, starting at 0.
public int SendCount = ___; // 👉 start the count at 0
public void Send(string to, string subject)
{
// 2) Record this call by increasing the counter by one.
___; // 👉 increment the counter: SendCount++
}
}
class SignupService
{
private readonly IEmailService _email;
public SignupService(IEmailService email) => _email = email;
public void Register(string name) => _email.Send(name + "@x.com", "Welcome!");
}
class Program
{
static void Main()
{
var fake = new FakeEmailService(); // the fake stands in for real email
var service = new SignupService(fake);
service.Register("alice");
service.Register("bob");
service.Register("carol");
// 3) Assert the service sent exactly three emails.
bool pass = fake.SendCount == ___; // 👉 we registered 3 users
Console.WriteLine($"SendCount = {fake.SendCount}");
Console.WriteLine(pass ? "PASS ✅" : "FAIL ❌");
// ✅ Expected output:
// SendCount = 3
// PASS ✅
}
}2. Stubs — Canned Data for Deterministic Tests
A stub solves a different problem from a spy. Where a spy watches calls going out, a stub feeds data in: it returns the same fixed value every time, so the method under test produces the same result on every run. That's what makes a test deterministic — it never depends on what's in a real database or what a remote API happens to return today. Here the stub always reports a price of £10, so you can assert the discount maths exactly. Fill in the two blanks.
using System;
// A STUB feeds CANNED data to the code under test so it behaves the same way
// every run — no real database, no randomness. We test the LOGIC, deterministically.
interface IPriceProvider
{
decimal GetPrice(string sku); // real version would hit a database
}
// 🎯 YOUR TURN — finish the stub so GetPrice always returns a fixed value.
class StubPriceProvider : IPriceProvider
{
public decimal GetPrice(string sku)
{
// 1) Ignore the sku and always return the same canned price: 10.00m
return ___; // 👉 a fixed decimal literal: 10.00m
}
}
// Logic under test: applies a discount to whatever price it is given.
class Checkout
{
private readonly IPriceProvider _prices;
public Checkout(IPriceProvider prices) => _prices = prices;
public decimal PriceWithDiscount(string sku, decimal percentOff)
=> _prices.GetPrice(sku) * (1 - percentOff / 100);
}
class Program
{
static void Main()
{
// Inject the STUB — now the price is always 10.00, so the test is deterministic.
var checkout = new Checkout(new StubPriceProvider());
// 2) 20% off a £10 item should be £8.00. Fill in the expected value.
decimal result = checkout.PriceWithDiscount("ABC", 20);
bool pass = result == ___; // 👉 the expected total: 8.00m
Console.WriteLine($"Price with 20% off = {result:F2}");
Console.WriteLine(pass ? "PASS ✅" : "FAIL ❌");
// ✅ Expected output:
// Price with 20% off = 8.00
// PASS ✅
}
}3. Moq — Setup, Returns & Verify
Hand-rolling a double for every interface gets tedious. Moq is the most popular .NET mocking library: new Mock<IFoo>() generates a fake implementation at runtime, and you reach the fake object via .Object. You stub a return value with .Setup(x => x.Method(arg)).Returns(value), and you assert an interaction happened with .Verify(x => x.Method(arg), Times.Once). The next two examples are real test code — read them and note the expected outcome in each comment (the runnable exercises above already let you practise the underlying idea by hand).
using Moq;
using Xunit;
public interface IUserRepository
{
User? GetById(int id);
void Add(User user);
}
public interface IEmailService
{
void SendWelcome(string email);
}
public class User { public int Id; public string Name = ""; public string Email = ""; }
// Service under test — depends only on interfaces (so they can be mocked).
public class UserService
{
private readonly IUserRepository _repo;
private readonly IEmailService _email;
public UserService(IUserRepository repo, IEmailService email) { _repo = repo; _email = email; }
public string? GetName(int id) => _repo.GetById(id)?.Name;
public void Register(string name, string email)
{
_repo.Add(new User { Name = name, Email = email });
_email.SendWelcome(email);
}
}
public class UserServiceTests
{
[Fact]
public void GetName_ReturnsName_FromStubbedRepo()
{
// Arrange — Moq builds a fake implementation of the interface for you.
var repo = new Mock<IUserRepository>();
var email = new Mock<IEmailService>();
// .Setup(...).Returns(...) = "when GetById(1) is called, return this user."
repo.Setup(r => r.GetById(1))
.Returns(new User { Id = 1, Name = "Alice" });
var service = new UserService(repo.Object, email.Object); // .Object = the fake
// Act
var name = service.GetName(1);
// Assert — STATE testing: we check the value that came back.
Assert.Equal("Alice", name);
// ✅ Test passes: the stubbed user's name was returned.
}
[Fact]
public void Register_AddsUser_AndSendsWelcome()
{
var repo = new Mock<IUserRepository>();
var email = new Mock<IEmailService>();
var service = new UserService(repo.Object, email.Object);
// Act
service.Register("Bob", "[email protected]");
// Assert — INTERACTION testing: we check the calls that were MADE.
// It.Is<User>(...) matches an argument that satisfies a predicate.
repo.Verify(r => r.Add(It.Is<User>(u => u.Name == "Bob")), Times.Once);
email.Verify(e => e.SendWelcome("[email protected]"), Times.Once);
// ✅ Test passes: Add ran exactly once with Bob, and a welcome email was sent.
}
}4. Verifying Calls: Times, Matchers & NSubstitute
Verify takes a Times argument — Times.Once, Times.Never, Times.Exactly(n) — to assert how many times a call happened. When you don't care about the exact argument, match any value with It.IsAny<T>() or a predicate with It.Is<T>(...). NSubstitute is a popular alternative with lighter syntax — no .Object, no Setup keyword; you stub with .Returns(...) and verify with .Received(n). Pick one per project and stay consistent.
using Moq;
using Xunit;
public class VerifyAndMatchersTests
{
[Fact]
public void Verify_CountsAndArgumentMatchers()
{
var email = new Mock<IEmailService>();
// Drive the mock a couple of times.
email.Object.SendWelcome("[email protected]");
email.Object.SendWelcome("[email protected]");
// Times.Exactly(n) — assert the EXACT number of calls.
// It.IsAny<string>() — match ANY string argument.
email.Verify(e => e.SendWelcome(It.IsAny<string>()), Times.Exactly(2));
// Times.Once / Times.Never — the two you'll use most.
email.Verify(e => e.SendWelcome("[email protected]"), Times.Once);
email.Verify(e => e.SendWelcome("[email protected]"), Times.Never);
// ✅ Test passes: 2 total sends, one to [email protected], none to nobody@.
}
[Fact]
public void NSubstitute_SameIdea_LessCeremony()
{
// NSubstitute is an alternative library — no .Object, no Setup keyword.
// Substitute.For<T>() builds the fake; you call methods directly on it.
var repo = NSubstitute.Substitute.For<IUserRepository>();
// Stub a return value — reads like plain English.
repo.GetById(1).Returns(new User { Name = "Zoe" });
var name = repo.GetById(1)?.Name;
Assert.Equal("Zoe", name);
// Verify an interaction with Received(n) instead of Verify(...).
repo.Received(1).GetById(1);
// ✅ Test passes: GetById(1) returned Zoe and was called exactly once.
}
}🔎 Deep Dive: state testing vs interaction testing
State testing asks "after I run this, is the result right?" You stub the inputs and assert on the value that comes back. It's robust: it doesn't care how the answer was reached, so you can refactor freely. Prefer it whenever you can.
Interaction testing asks "did my code call the right collaborator the right way?" You Verify the calls. It's essential when the whole point of a method is a side effect — sending an email, writing to a queue — where there's no return value to inspect. But it couples your test to how the code works, so use it only for genuine, meaningful side effects.
// STATE: stub the input, assert the output.
repo.Setup(r => r.GetById(1)).Returns(new User { Name = "Al" });
Assert.Equal("Al", service.GetName(1));
// INTERACTION: drive the code, assert the call happened.
service.Register("Bob", "[email protected]");
email.Verify(e => e.SendWelcome("[email protected]"), Times.Once);Rule of thumb: verify a side effect, but assert a return value. If you find yourself verifying every call a method makes, you're probably re-stating the implementation in your test — that's the brittleness trap below.
🔎 Deep Dive: when NOT to mock
Mocking is a tool, not a default. Reach for it only when the real thing is slow, non-deterministic, has side effects, or isn't built yet. Otherwise, the real object makes a better test. Skip the mock when:
- • The dependency is pure and fast — a calculator, a formatter, a value object. Use the real one; a mock just adds noise.
- • You'd be mocking a type you don't own (a third-party SDK, HttpClient, DbContext). Wrap it in your own thin interface and mock that instead.
- • You're tempted to mock the class under test (or part of it). If you have to, the class is doing too much — split it.
- • An in-memory fake reads better than a pile of Setup lines — e.g. a real List<T>-backed repository.
For things like databases, an integration test against a real (or in-memory) database often gives more confidence than a mock that just parrots back what you told it.
Pro Tips
- 💡 Mock interfaces, not concrete classes: Moq can only override virtual/abstract members, so a sealed concrete class can't be mocked. Depend on an interface and you're free.
- 💡 Prefer a stub to a mock when you can assert on a return value. Verifying calls couples the test to the implementation; asserting state doesn't.
- 💡 Only own-types get mocked. Wrap third-party SDKs and HttpClient behind your own interface, then mock the interface — never the library.
- 💡 One mock per dependency, not per call. If a single test sets up five mocks, the class under test probably has too many responsibilities.
- 💡 Use MockBehavior.Strict sparingly: new Mock<IRepo>(MockBehavior.Strict) throws on any unexpected call. It catches accidents but makes tests fragile, so reserve it for when call-completeness really matters.
Common Errors (and the fix)
- Over-mocking: a test with six mocks and ten Setup lines tests your mocks, not your code. Mock only the dependencies that are slow or have side effects; use real objects for the rest.
- Mocking what you don't own: mocking a third-party SDK or HttpClient directly is brittle — the library changes and your tests break for no real reason. Wrap it in your own interface and mock that.
- Brittle interaction tests: verifying every internal call means any refactor breaks the test even though behaviour is unchanged. Verify only the meaningful side effects (the email was sent); assert the return value for everything else.
- Mocking concrete classes: "System.NotSupportedException: Unsupported expression… non-overridable members may not be used in setup" means you tried to Setup a non-virtual method on a concrete class. Mock an interface (or make the member virtual).
- Forgetting .Object: passing the Mock<IFoo> itself where an IFoo is expected won't compile. Pass mock.Object — the generated fake — into your service.
📋 Quick Reference
| Task | Moq | NSubstitute |
|---|---|---|
| Create a double | new Mock<IFoo>() | Substitute.For<IFoo>() |
| Get the fake object | mock.Object | (the substitute itself) |
| Stub a return | .Setup(x => x.M()).Returns(v) | sub.M().Returns(v) |
| Match any arg | It.IsAny<int>() | Arg.Any<int>() |
| Verify once | .Verify(x => x.M(), Times.Once) | sub.Received(1).M() |
| Verify never | .Verify(x => x.M(), Times.Never) | sub.DidNotReceive().M() |
Both libraries do the same job. It.IsAny<T>()/Arg.Any<T>() match any argument; It.Is<T>(predicate)/Arg.Is<T>(predicate) match by a condition.
Frequently Asked Questions
Q: What's the real difference between a stub and a mock?
A stub feeds canned data in so the code under test is deterministic — you then assert on the result. A mock records the calls made to it so you can Verify the right interactions happened. Stub = state testing; mock = interaction testing. Moq can do both from one Mock<T>.
Q: Why can't I mock a concrete class?
Moq works by generating a subclass and overriding members, so it can only override virtual or abstract ones. A normal concrete method isn't overridable, so the setup throws. Depend on an interface (or mark members virtual) and the problem disappears.
Q: Should I mock HttpClient or my DbContext?
No — those are types you don't own, and mocking them directly is painful and brittle. Wrap them behind a small interface of your own (IWeatherApi, IUserRepository) and mock that. For a database, an in-memory or integration test often gives more confidence than any mock.
Q: My tests break every time I refactor — what am I doing wrong?
You're likely over-using interaction testing — verifying internal calls that are implementation detail. Verify only the meaningful side effects and assert on return values for the rest. Tests should track behaviour, not the exact sequence of method calls.
Q: Do I even need a framework, or can I write fakes by hand?
You can — and for simple interfaces a hand-rolled fake is often clearer (you saw two above). Frameworks like Moq pay off when an interface is large or you need flexible argument matching and call counts without writing all that boilerplate yourself.
Mini-Challenge: Fake Repository
No blanks this time — just a brief and an outline. Build an in-memory FakeRepository that records saved items, inject it into NoteService, add two notes, and assert your fake recorded exactly two with the right first item. Run it and check your output against the expected lines in the comments.
using System;
using System.Collections.Generic;
// 🎯 MINI-CHALLENGE: test a service using a hand-rolled fake repository.
//
// 1. Implement IRepository with an in-memory fake:
// - back it with a List<string> field called Saved
// - Save(item) should add the item to that list
// - Count() should return how many items have been saved
// 2. In Main, inject your fake into the NoteService below.
// 3. Call service.AddNote("buy milk") and service.AddNote("call mum").
// 4. Assert the fake recorded EXACTLY 2 saved notes, and that the first
// saved note is "buy milk".
// 5. Print PASS or FAIL.
//
// ✅ Expected output:
// Saved count = 2
// PASS ✅
interface IRepository
{
void Save(string item);
int Count();
}
class FakeRepository : IRepository
{
// your fields and methods here
}
class NoteService
{
private readonly IRepository _repo;
public NoteService(IRepository repo) => _repo = repo;
public void AddNote(string text) => _repo.Save(text);
}
class Program
{
static void Main()
{
// your code here
}
}🎉 Lesson Complete
- ✅ Five test doubles: dummy, stub, fake, mock, spy
- ✅ A double is just a class implementing the same interface, swapped in via dependency injection
- ✅ Moq: new Mock<IFoo>(), .Setup(...).Returns(...), .Verify(..., Times.Once), .Object
- ✅ Stubs feed data in (state testing); mocks/spies record calls (interaction testing)
- ✅ When not to mock: pure/fast types, types you don't own, the class under test itself
- ✅ Avoid over-mocking and brittle interaction tests — verify side effects, assert return values
Practice quiz
What is a test double?
- A duplicate test file
- A second assertion
- A stand-in for a real dependency you can control and inspect
- A doubled test result
Answer: A stand-in for a real dependency you can control and inspect. A test double is a controlled stand-in for a real dependency, like a crash-test dummy for a person.
What does a stub do?
- Returns canned, fixed data so the code under test is deterministic
- Records the calls made to it
- Sends real emails
- Throws on every call
Answer: Returns canned, fixed data so the code under test is deterministic. A stub feeds canned data in so the method under test produces the same result every run (state testing).
What does a mock/spy let you assert on?
- Only return values
- The compiler output
- Network latency
- The calls (interactions) that were made
Answer: The calls (interactions) that were made. A mock/spy records calls going out so you can verify the right interactions happened (interaction testing).
What technique makes a dependency swappable for a double in a test?
- Reflection
- Dependency injection — passing the dependency through the constructor
- Inheritance
- Static fields
Answer: Dependency injection — passing the dependency through the constructor. The class under test takes its dependency via its constructor, so a test can inject a double instead of the real thing.
In Moq, how do you stub a return value?
- .Setup(x => x.M(arg)).Returns(value)
- .Verify(...)
- .Object
- .Received(1)
Answer: .Setup(x => x.M(arg)).Returns(value). Moq stubs a return with .Setup(x => x.Method(arg)).Returns(value).
In Moq, how do you get the fake object to pass into your service?
- mock itself
- mock.Fake
- mock.Object
- new Mock()
Answer: mock.Object. mock.Object is the generated fake implementing the interface; passing the Mock<T> itself won't compile.
Why can't Moq mock a non-virtual method on a concrete class?
- It's too slow
- Moq overrides members in a subclass, so it can only override virtual/abstract ones
- Concrete classes are sealed
- It mocks fields instead
Answer: Moq overrides members in a subclass, so it can only override virtual/abstract ones. Moq generates a subclass and overrides members, which only works for virtual/abstract members — so mock interfaces instead.
What does Verify(..., Times.Once) assert?
- The method returns once
- The method is never called
- The test passes once
- The call happened exactly one time
Answer: The call happened exactly one time. Times.Once asserts the verified call was made exactly one time (interaction testing).
What's the rule of thumb for state vs interaction testing?
- Always verify every call
- Verify a side effect, but assert a return value
- Never use mocks
- Assert nothing
Answer: Verify a side effect, but assert a return value. Verify meaningful side effects (e.g. the email was sent), but assert on return values for everything else.
When should you NOT mock a dependency?
- When it's a third-party SDK
- When it has side effects
- When it's pure and fast (a calculator/formatter) — use the real one
- When it's slow
Answer: When it's pure and fast (a calculator/formatter) — use the real one. Skip mocking pure, fast types; mocking them just adds noise. Mock only slow, non-deterministic, or side-effecting dependencies.
Continue this course
- Previous: Unit Testing Mastery (xUnit, NUnit, MSTest)
- Next: Test-Driven Development in Real Projects — Apply Red-Green-Refactor TDD to real C# feature development
- Quick reference: C# cheat sheet
- From the blog: Dependency Injection in C#: A Complete Guide