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.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
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.
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.
"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.
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.
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.
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:
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.
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.
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.
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.
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.