Mocking with Moq: Missing Setup Returns Null in Production
Missing mock setup for a new method overload caused a NullReferenceException in production.
- 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.
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.
Advanced Patterns — Callbacks, Sequences, and Recursive Mocks
Callbacks allow you to inspect or modify the arguments passed to the mocked method. Use .Callback<T1, T2>(...) with matching argument types to capture values. This is invaluable for testing code that pushes data into a dependency — you can assert what was sent.
Sequences model multi-step interactions where the same method must return different values on successive calls. SetupSequence() accepts a chain of Returns, Throws, or Callbacks. After the last item, subsequent calls throw by default (unless you add a final Returns for covering).
Recursive mocks happen when a mock returns another mock. If your interface returns an interface, and that interface returns another, you can set up the chain automatically using DefaultValue.Mock. This reduces boilerplate but can hide deep failures.
Production Gotchas — MockBehavior, Async, and Timeouts
Even with solid test coverage, Moq can betray you in production. The most common incidents:
- Loose behavior masking missing setups — your test passes because Moq returns null/0 for unset methods, but production code expects a real object.
- Async mocks not awaited — a method returning Task but not awaited in the SUT means the mock's ReturnsAsync is never observed; the test may pass but production runs synchronously or deadlocks.
- Time-dependent mocks — mocking DateTime.Now or similar static calls is impossible with Moq; you need to inject an abstraction (e.g., ISystemClock).
- Out/ref parameters — hard to set up and verify. Use It.Ref<T>.IsAny for ref matching and store output values via Callback.
Senior engineers always run their test suite with both mock behaviors at least once during CI to catch Loose-vs-Strict discrepancies.
| Feature | When to Use | Risk if Overused |
|---|---|---|
| MockBehavior.Loose | Quick prototyping, rarely used code paths | Missing setups hide production bugs |
| MockBehavior.Strict | Critical interfaces, contract tests | Brittle tests, high maintenance |
| SetupSequence | State machines, read-once streams | Order-dependent tests fail unexpectedly |
| Callback | Capturing arguments, complex validation | Shared state across concurrent tests |
| DefaultValue.Mock | Deep dependency chains, no need to customize intermediate mocks | Difficult to trace where the real return value came from |
Key Takeaways
- Moq creates proxy implementations — Setup defines behavior, Verify asserts invocations.
- Strict mode catches missing setups early; Loose mode hides them until production crashes.
- Use Callback for side effects, SetupSequence for ordered returns, and DefaultValue.Mock for deep chains.
- Async mocks require the SUT to actually await — otherwise the ReturnsAsync never fires.
- Inject abstractions for time, random, and other static dependencies — Moq can't mock statics.
- Run test suite under both Loose and Strict during CI to uncover discrepancies.
Common Mistakes to Avoid
- Using Loose mock behavior as default and never switching
Symptom: Tests pass but production code crashes with NullReferenceException when a new method is called on a mock that hasn't been set up. The default returns null, masking the missing setup.
Fix: Use MockBehavior.Strict as the default for all unit tests. Only switch to Loose for specific setups after careful deliberation, and add a comment explaining why. - Not awaiting async mock methods in the SUT
Symptom: Unit test passes, but in production the method runs synchronously, potentially deadlocking or returning incomplete results. The mock's ReturnsAsync never fires.
Fix: Ensure the SUT has 'await' before every call to an async method on a mocked dependency. Use analyzers like 'CA2007' to catch missing awaits during compilation. - Using SetupSequence without a fallback for extra calls
Symptom: If the production code calls the method more times than the sequence provides, Moq throws an exception at runtime, even in Loose mode. This often happens during retries or edge cases.
Fix: Always add a terminal .Returns()at the end of the sequence, or use a .Callback to track call count and conditionally return values from a list.
Interview Questions on This Topic
- QExplain the difference between MockBehavior.Loose and MockBehavior.Strict in Moq. When would you choose one over the other?SeniorReveal
- QHow do you mock a method that takes an out parameter in Moq? Provide an example.Mid-levelReveal
- QWhat is the difference between
Verify()andVerifyAll()in Moq? When should you use each?SeniorReveal
Frequently Asked Questions
What's the difference between Moq and a handwritten fake?
A handwritten fake implements the interface manually with your own logic. Moq generates the implementation at runtime via Castle.Core dynamic proxy. Handwritten fakes give you full control but require maintenance as the interface changes. Moq is faster to write and scales better, but you're limited to what the library supports (no custom property visibility, no static methods). For production-heavy DI scenarios, Moq saves days of boilerplate.
Can Moq mock static methods or sealed classes?
No — Moq relies on overriding virtual methods via proxy generation. Static methods can't be overridden. Sealed classes can't be inherited. For static dependencies, wrap them in an interface or use a delegate (e.g., Func<T>) that you can mock. Alternatively, use a testing framework like Microsoft Fakes (Shims) or Pose, but those are heavier and slower.
How do I mock a method that returns Task of IEnumerable but the implementation returns an empty list?
Use mock.Setup(x => x. or GetItemsAsync()).ReturnsAsync(new List<Item>());mock.Setup(x => x.. The first is cleaner. Ensure the SUT awaits the task, otherwise you'll get a Task without callbacks.GetItemsAsync()).Returns(Task.FromResult(Enumerable.Empty<Item>()));
That's Testing. Mark it forged?
3 min read · try the examples if you haven't