Mocking
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 examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 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.
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.
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.
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).
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.
🔎 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.
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> .
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.
🎉 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
- ✅ Next lesson: Test-Driven Development — write the failing test first, then the code
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
- Next: Test-Driven Development