Mocking with Moq: Missing Setup Returns Null in Production
Missing mock setup for a new method overload caused a NullReferenceException in production.
20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.
- ✓Deep production experience
- ✓Understanding of internals and trade-offs
- ✓Experience debugging complex systems
- Moq creates fake implementations of interfaces or abstract classes for isolated unit tests.
- Setup defines expectations: mock.Setup(x => x.Method()).Returns(value).
- Verification ensures methods were called with expected arguments and counts.
- Callbacks execute code when a mocked method is invoked, useful for side effects.
- Sequences model ordered calls with SetupSequence, perfect for state machines.
- Production trap: Loose mocks silently return default values hiding missing setups.
Imagine you're testing a new recipe but you don't want to use real expensive ingredients every time — so you use plastic fruit that looks and behaves exactly like the real thing. Moq is that plastic fruit for your C# code. It creates fake versions of your dependencies (databases, APIs, email services) that behave exactly how you tell them to, so you can test your logic in total isolation without touching anything real.
Every serious C# application talks to things it doesn't control — databases that can go offline, payment APIs that cost money per call, email servers that send real emails to real people. When you want to test the logic that orchestrates all those moving parts, you can't just fire up the real infrastructure for every test run. That's not just slow — it's unpredictable, expensive, and a maintenance nightmare. This is the problem unit testing was born to solve, and it's the reason mocking frameworks exist. Moq is the most popular mocking library in .NET. It's mature, flexible, and it handles the vast majority of what you'll need. But it's not magic. Without understanding its internals — how Setup, Returns, Verify, Callback, and SetupSequence actually work — you'll write tests that pass in isolation but fail in production. This guide goes deep into those mechanics and the patterns that separate senior engineers from the rest.
What Is Mocking with Moq in C#?
Moq (pronounced 'mock-you') is a .NET library that generates proxy implementations of interfaces and abstract classes at runtime. When you call a method on the proxy, Moq intercepts it and returns the value you specified via Setup. This lets you replace real dependencies — a database context, an HTTP client, a file system — with a controllable fake. The two fundamental operations are:
- Setup — tell the mock how to behave when a method is called.
- Verify — assert that the method was called with the expected arguments.
- Loose (default) — returns default values for unmatched calls. Dangerous because missing setups hide failures.
- Strict — throws an exception for any call without a Setup. Harder to maintain but safe.
Senior engineers almost always start with Strict in new tests and loosen only when there's a good reason, typically to reduce boilerplate for test utility methods.
Setting Up Mocks — Returns, Throws, and Parameters
Setup is the core operation. You express: 'When method X is called with arguments matching these conditions, do Y.' The matching engine supports exact values, predicate expressions, and wildcard matchers like It.IsAny<T>(), It.Is<T>(predicate), and It.IsInRange<T>(min, max).
Returns() specifies the return value or an expression that computes it lazily. Throws() makes the mock throw an exception. For void methods, you call .Callback to execute side effects, though Returns is not applicable.
Parameters can be matched by value, condition, or any. Be careful with reference types — Moq uses Equals() for matching, so custom objects need proper Equals override or use It.Is<T>(x => x.SomeProperty == expected).
Verification — Ensuring Methods Were Called
Verification is the second pillar. After the SUT runs, you ask: 'Was method X called exactly N times with these arguments?' The Verify method takes the same expression as Setup, plus an optional Times constraint. Without the times parameter, it defaults to Times.AtLeastOnce().
- Verifying a call that was _not_ set up will throw a MockException even if the code never calls it — because the default behavior for unmatched calls in Strict mode is to throw at call time, not verify time.
- Verifying with It.IsAny<T>() but the actual argument is null — It.IsAny<T>() matches null when T is nullable, but not for value types (struct).
VerifyAll()vsVerify()— VerifyAll checks all setups, including those not explicitly verified. Use it sparingly to avoid brittle tests.