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.
- 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.
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.
Partial Mocks — When You Need a Real Object with a Few Fakes
You're testing a legacy payment service that makes a database call you can't untangle easily. You don't want to mock the entire thing — that's 14 interfaces and a prayer. You need one real method and one fake.
Partial mocks let you create a real instance of a class and override specific members. Moq does this with As() or by passing a mock to the constructor. The rule: default behavior is real, setup behavior overrides it.
Why reach for this? When you own the code and refactoring isn't on the table. When a third-party SDK has 50 virtual methods and you only need to intercept two. It's surgical.
Warning: partial mocks are a code smell. If you're doing this more than once a quarter, your design is broken. The real fix is an interface. But for today's hotfix at 2 AM, this is what saves your ass.
Mocking Internals and Protected Members — Breaking the Barrier
You need to test an internal method that does the heavy lifting. Someone sealed the class. The method is protected virtual. Moq doesn't care about your access modifiers if you use Mock..Protected()
This is for white-box testing. You're asserting how the sausage is made, not just what comes out. You're doing it because the public interface is too coarse to isolate the bug.
Setup a protected member: mock.. Verification works the same way. The string name must match exactly. No refactoring safety. No compiler errors if you rename it. That's the cost.Protected().Setup<bool>("InternalCheck", ItExpr.IsAny<int>()).Returns(true)
Senior shortcut: use this only when you control the source and the test proves a specific logic path. If you find yourself mocking internals of a third-party assembly, stop. You're doing integration testing in a unit test harness. That's a different tool.
nameof() for the method string when possible. It's not perfect for protected, but it catches typos at compile time. Example: mock.Protected().Setup<bool>(nameof(ValidateReport)...) — doesn't work for protected, so fall back to const string. Painful but safer.Protected() when you must test internals. Accept the string-based fragility.The Silent ReturnNull — When Missing Mock Setup Breaks Production
- Never assume all mock methods are covered unless you use Strict mode.
- Add a test that explicitly exercises every branch that calls each mocked interface method.
- Review mock setups when the interface changes — treat missing setups as production bugs.
Returns() for all overloads. Also consider using DefaultValue.Mock for recursive mocks.var mock = new Mock<IService>(MockBehavior.Strict);mock.SetupAllProperties(); // for property stubsKey takeaways
Common mistakes to avoid
3 patternsUsing Loose mock behavior as default and never switching
Not awaiting async mock methods in the SUT
Using SetupSequence without a fallback for extra calls
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
Explain the difference between MockBehavior.Loose and MockBehavior.Strict in Moq. When would you choose one over the other?
Frequently Asked Questions
20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.
That's Testing. Mark it forged?
5 min read · try the examples if you haven't