Home C# / .NET Mocking with Moq: Missing Setup Returns Null in Production
Advanced 5 min · March 06, 2026
Mocking with Moq in C#

Mocking with Moq: Missing Setup Returns Null in Production

Missing mock setup for a new method overload caused a NullReferenceException in production.

N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 30 min
  • Deep production experience
  • Understanding of internals and trade-offs
  • Experience debugging complex systems
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • 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.
✦ Definition~90s read
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:

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.

1. Setup — tell the mock how to behave when a method is called. 2. Verify — assert that the method was called with the expected arguments.

Moq supports two mock behaviors: - 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.

Plain-English First

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:

  1. Setup — tell the mock how to behave when a method is called.
  2. Verify — assert that the method was called with the expected arguments.
Moq supports two mock behaviors
  • 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.

TheCodeForge/MoqDemo/BasicMock.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace TheCodeForge.MoqDemo;

public interface IEmailService
{
    void Send(string to, string subject, string body);
}

// Test example
[Fact]
public void Send_email_calls_service()
{
    var mock = new Mock<IEmailService>(MockBehavior.Strict);
    mock.Setup(m => m.Send("user@example.com", "Hi", It.IsAny<string>()));

    var service = new NotificationService(mock.Object);
    service.SendWelcomeEmail("user@example.com");

    mock.Verify(m => m.Send("user@example.com", "Hi", It.IsAny<string>()), Times.Once);
}
Output
Test passes — proves Send was called exactly once with expected arguments.
📊 Production Insight
Strict mode catches missing setups at test time, not in production.
Loose mode hides bugs until NullReferenceException appears in logs.
Rule: default to Strict, relax only when absolutely necessary.
🎯 Key Takeaway
Moq intercepts virtual method calls on generated proxies.
Setup defines behavior; Verify asserts invocation.
Strict is safe; Loose is fast — know the trade-off.
mocking-moq-csharp Moq Mocking Architecture Layers Component hierarchy for mocking with Moq in C# Test Layer Unit Test | Integration Test Mocking Framework Moq | MockBehavior | Setup API Mock Configuration Returns | Throws | Callbacks Verification Verify | Times | SetupAllProperties Production Code Real Object | Partial Mock | Protected Members THECODEFORGE.IO
thecodeforge.io
Mocking Moq Csharp

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).

TheCodeForge/MoqDemo/SetupExamples.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace TheCodeForge.MoqDemo;

public interface IOrderRepository
{
    Order GetById(int id);
    void Save(Order order);
}

// Test: setup with predicate and Returns
[Fact]
public void GetById_returns_specific_order()
{
    var mock = new Mock<IOrderRepository>(MockBehavior.Strict);
    mock.Setup(r => r.GetById(It.Is<int>(id => id > 0)))
        .Returns<int>(id => new Order { Id = id, Amount = 100 });

    var result = mock.Object.GetById(42);
    Assert.Equal(100, result.Amount);
}

// Setup throws
mock.Setup(r => r.Save(It.IsAny<Order>()))
    .Throws<DbConcurrencyException>();
Output
The GetById test passes only for IDs > 0; Save throws expected exception.
📊 Production Insight
Using It.Is<T>() with a predicate can become slow if the predicate is expensive.
For hundreds of calls, prefer exact matching or a simple It.IsAny<T>() to avoid test slowdown.
Rule: keep predicates cheap and avoid database calls in predicates.
🎯 Key Takeaway
Setup matching is the heart of Moq.
Use It.Is for flexible matching, exact values for precision.
Lazy Returns with expressions keep tests DRY.

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().

Common pitfalls
  • 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() but the actual argument is null — It.IsAny() matches null when T is nullable, but not for value types (struct).
  • VerifyAll() vs Verify() — VerifyAll checks all setups, including those not explicitly verified. Use it sparingly to avoid brittle tests.
TheCodeForge/MoqDemo/VerificationExamples.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace TheCodeForge.MoqDemo;

[Fact]
public void Verify_multiple_invocations()
{
    var mock = new Mock<ILogger>(MockBehavior.Loose);
    var service = new Processor(mock.Object);
    
    service.RunBatch(new[] { 1, 2, 3 });
    
    // Verify exactly 3 calls to LogInfo
    mock.Verify(l => l.LogInfo(It.IsAny<string>()), Times.Exactly(3));
    // Verify LogError was never called
    mock.Verify(l => l.LogError(It.IsAny<string>()), Times.Never);
}
Output
Test passes because RunBatch logged three times and no errors.
📊 Production Insight
Over-verification leads to brittle tests — changing implementation details breaks tests.
Only verify calls that are part of the contract, not internal side effects.
Rule: verify the 'what', not the 'how' — prefer state-based assertions when possible.
🎯 Key Takeaway
Verify checks method call counts and arguments.
Times.Once, Times.Exactly, Times.Never are your tools.
Don't verify implementation details — test behavior, not calls.
mocking-moq-csharp THECODEFORGE.IO Moq Mocking Architecture Layers Layered view of mock setup, verification, and production behavior Test Layer Mock Creation | Setup Returns | Setup Throws Verification Layer Verify Method Calls | Times Constraints | Callback Assertions Advanced Patterns Callbacks | Sequences | Recursive Mocks Production Layer MockBehavior.Loose | Async Gotchas | Time-sensitive Mocks Edge Cases Partial Mocks | Protected Members | Internals THECODEFORGE.IO
thecodeforge.io
Mocking Moq Csharp

Advanced Patterns — Callbacks, Sequences, and Recursive Mocks

Callbacks allow you to inspect or modify the arguments passed to the mocked method. Use .Callback(...) 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.

TheCodeForge/MoqDemo/AdvancedPatterns.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace TheCodeForge.MoqDemo;

public interface IStateMachine
{
    State GetNext();
}

[Fact]
public void Sequence_returns_in_order()
{
    var mock = new Mock<IStateMachine>(MockBehavior.Strict);
    mock.SetupSequence(m => m.GetNext())
        .Returns(State.Initial)
        .Returns(State.Processing)
        .Returns(State.Final);

    Assert.Equal(State.Initial, mock.Object.GetNext());
    Assert.Equal(State.Processing, mock.Object.GetNext());
    Assert.Equal(State.Final, mock.Object.GetNext());
}

//Callback to capture argument
var callbackArg = string.Empty;
mock.Setup(x => x.Log(It.IsAny<string>()))
    .Callback<string>(msg => callbackArg = msg);
Output
Sequence test verifies ordered state transitions; callback captures the log argument.
📊 Production Insight
Sequences are powerful but brittle across concurrent tests — each call modifies shared state.
Callback closures can cause subtle bugs if the captured variable is reused across tests.
Rule: reset captured state in test setup (collection fixture) or use local variables per test.
🎯 Key Takeaway
Callbacks capture arguments for later assertions.
SetupSequence models ordered interactions.
Recursive mocks reduce boilerplate but increase test complexity.

Production Gotchas — MockBehavior, Async, and Timeouts

Even with solid test coverage, Moq can betray you in production. The most common incidents:

  1. Loose behavior masking missing setups — your test passes because Moq returns null/0 for unset methods, but production code expects a real object.
  2. 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.
  3. Time-dependent mocks — mocking DateTime.Now or similar static calls is impossible with Moq; you need to inject an abstraction (e.g., ISystemClock).
  4. 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.

TheCodeForge/MoqDemo/ProductionGotchas.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace TheCodeForge.MoqDemo;

// Async setup
mock.Setup(x => x.GetDataAsync()).ReturnsAsync(new Data());

// SUT must await, else test may pass but production runs sync
// public async Task<Data> Fetch() => await _dependency.GetDataAsync();

// Ref parameter
mock.Setup(x => x.TryGetValue(It.IsAny<int>(), out It.Ref<int>.IsAny))
    .Callback(new InvocationAction(invocation =>
    {
        invocation.Arguments[1] = 42; // assign output
    }))
    .Returns(true);
Output
Async mock works only if awaited; ref parameters need special handling.
📊 Production Insight
Switching from Loose to Strict can reveal 10-20 missing setups in an established codebase.
Address them by either adding the missing Setup (preferred) or switching individual methods to Loose.
Rule: keep a 'StrictMode' global switch in your test base class for troubleshooting.
🎯 Key Takeaway
Loose hides bugs; Strict reveals them.
Async mocks require proper awaiting in SUT.
Inject abstractions for time, random — don't mock statics.

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.

PartialPaymentService.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// io.thecodeforge — csharp tutorial

public class PaymentProcessor
{
    public virtual bool Authorize(string card)
    {
        return ExternalSdk.Call(card); // Real call
    }

    public virtual bool Refund(string transactionId)
    {
        throw new NotImplementedException(); // Legacy junk
    }
}

[Test]
public void Refund_Fails_Gracefully()
{
    var mock = new Mock<PaymentProcessor>();
    mock.Setup(p => p.Refund(It.IsAny<string>()))
        .Returns(false);

    // Authorize uses real implementation
    // Refund uses our fake
    var processor = mock.Object;
    var refunded = processor.Refund("txn_abc");

    Assert.That(refunded, Is.False);
}
Output
Test passes. Refund returns false.
Authorize still calls ExternalSdk (if invoked).
⚠ Legacy Trap:
Partial mocks require virtual methods. If your class doesn't have them, you can't mock. That's your hint to introduce an interface. Do it before the code rots further.
🎯 Key Takeaway
Partial mocks = real instance + selective overrides. Use only when refactoring is blocked.

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.Protected().Setup("InternalCheck", ItExpr.IsAny()).Returns(true). 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.

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.

ProtectedMockScenario.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// io.thecodeforge — csharp tutorial

public class ReportEngine
{
    protected virtual bool ValidateReport(string reportId)
    {
        // Complex validation logic
        return reportId.StartsWith("RPT");
    }

    public string Generate(string reportId)
    {
        return ValidateReport(reportId) ? "OK" : "FAIL";
    }
}

[Test]
public void Generate_Returns_OK_When_Validation_Passes()
{
    var mock = new Mock<ReportEngine>();
    mock.Protected()
        .Setup<bool>("ValidateReport", ItExpr.IsAny<string>())
        .Returns(true);

    var result = mock.Object.Generate("RPT_001");

    Assert.That(result, Is.EqualTo("OK"));
}
Output
Test passes.
ReportEngine.Generate returns "OK".
💡Senior Shortcut:
Use 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.
🎯 Key Takeaway
Mock protected members with .Protected() when you must test internals. Accept the string-based fragility.

Mock.Of() for One-Line Mock Creation

Moq provides a concise syntax for creating mocks in a single line using Mock.Of<T>(). This is particularly useful when you need a simple mock without explicit setup, or when you want to inline mock creation in test parameters. For example, var mock = Mock.Of<IFoo>(); creates a mock with default behavior (all methods return default values). You can also set up expectations directly: var mock = Mock.Of<IFoo>(m => m.Name == "test" && m.DoSomething() == true);. This approach reduces boilerplate and improves readability, especially in Arrange-Act-Assert patterns. However, be cautious: Mock.Of<T>() returns the mocked object directly, not the Mock<T> wrapper, so you lose access to verification and advanced setup. For scenarios where you only need the mock as a stub, this is ideal. In production, use Mock.Of<T>() for simple dependencies in unit tests where you don't need to verify interactions.

MockOfExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface IFoo
{
    string Name { get; set; }
    bool DoSomething();
}

// One-line mock creation with default behavior
var foo = Mock.Of<IFoo>();

// One-line mock with setup
var fooWithSetup = Mock.Of<IFoo>(m => m.Name == "test" && m.DoSomething() == true);

// Usage
Console.WriteLine(fooWithSetup.Name); // "test"
Console.WriteLine(fooWithSetup.DoSomething()); // true
💡When to Use Mock.Of()
📊 Production Insight
In production codebases, Mock.Of<T>() is best for read-only dependencies or when you need to quickly satisfy constructor parameters without complex setup.
🎯 Key Takeaway
Mock.Of<T>() creates a mock object in one line, ideal for simple stubs, but sacrifices access to the Mock<T> wrapper for verification.

AutoMock with AutoFixture + Moq for Cleaner Tests

AutoFixture with the AutoMoq extension automates the creation of mocks and test data, reducing boilerplate in unit tests. By combining AutoFixture's data generation with Moq's mocking, you can create SUTs (System Under Test) with minimal setup. For example, using var fixture = new Fixture().Customize(new AutoMoqCustomization());, you can then call fixture.Create<MyClass>() to automatically create an instance of MyClass with all its dependencies mocked. This is especially powerful for classes with many dependencies, as it eliminates repetitive mock creation. You can also inject specific setups using fixture.Freeze<Mock<IDependency>>() to control behavior. In production, AutoMock reduces test maintenance when dependencies change, as new interfaces are automatically mocked. However, be aware that it can hide important setup details, so use it judiciously for integration-style tests or when dependencies are trivial.

AutoMockExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using AutoFixture;
using AutoFixture.AutoMoq;

public class MyService
{
    private readonly IDependency _dep;
    public MyService(IDependency dep) => _dep = dep;
    public int Compute() => _dep.GetValue() * 2;
}

public interface IDependency { int GetValue(); }

// Arrange
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var mockDep = fixture.Freeze<Mock<IDependency>>();
mockDep.Setup(d => d.GetValue()).Returns(5);
var sut = fixture.Create<MyService>();

// Act
var result = sut.Compute();

// Assert
Assert.Equal(10, result);
🔥AutoFixture + Moq: A Powerful Duo
📊 Production Insight
In production, use AutoMock for integration tests or when testing complex object graphs. For critical business logic, prefer explicit mocks to ensure clarity and control.
🎯 Key Takeaway
AutoFixture with AutoMoq automates mock creation, making tests cleaner and more maintainable, especially for classes with many dependencies.
MockBehavior.Loose vs Strict in Moq Trade-offs between flexibility and safety in production MockBehavior.Loose MockBehavior.Strict Unmatched Method Call Returns default (null for ref types) Throws MockException Setup Required Only for specific return values All calls must be set up Test Flexibility High: ignores uninteresting calls Low: every call must be defined Production Safety Silent nulls can hide bugs Fails fast on unexpected calls Async Methods Returns null Task, may cause NRE Throws immediately if not set up Recommended Use Quick unit tests with few setups Critical paths needing full coverage THECODEFORGE.IO
thecodeforge.io
Mocking Moq Csharp

MockBehavior.Strict vs Loose: When to Use Each

Moq supports two mock behaviors: MockBehavior.Loose (default) and MockBehavior.Strict. Loose mocks return default values (e.g., null, 0, false) for any method or property that hasn't been set up, without throwing exceptions. Strict mocks throw an exception if any member is called without a corresponding setup. Use Loose mocks for most tests where you only care about specific interactions and want to avoid clutter. Strict mocks are useful when you want to ensure that only expected methods are called, often in behavior verification scenarios. For example, var mock = new Mock<IFoo>(MockBehavior.Strict); will throw if mock.Object.DoSomething() is called without a setup. In production, Loose is preferred for its flexibility; Strict can be too brittle for large codebases. However, Strict can catch unintended calls during refactoring. Choose based on test intent: Loose for state-based testing, Strict for interaction-based testing.

MockBehaviorExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface IFoo { int GetValue(); void DoSomething(); }

// Loose mock (default) - returns 0 for GetValue, no exception for DoSomething
var looseMock = new Mock<IFoo>(MockBehavior.Loose);
Console.WriteLine(looseMock.Object.GetValue()); // 0
looseMock.Object.DoSomething(); // no exception

// Strict mock - throws if method not set up
var strictMock = new Mock<IFoo>(MockBehavior.Strict);
// strictMock.Object.GetValue(); // throws MockException

// Setup to avoid exception
strictMock.Setup(m => m.GetValue()).Returns(42);
Console.WriteLine(strictMock.Object.GetValue()); // 42
⚠ Strict Mocks Can Be Fragile
📊 Production Insight
In production, prefer Loose mocks to reduce test maintenance. Reserve Strict mocks for contracts where unexpected calls indicate bugs, such as in state machines or protocol implementations.
🎯 Key Takeaway
Use Loose mocks by default for flexibility; use Strict mocks when you need to verify that only expected methods are called, but be aware of brittleness.
● Production incidentPOST-MORTEMseverity: high

The Silent ReturnNull — When Missing Mock Setup Breaks Production

Symptom
Unit tests pass, but in production the application crashes with NullReferenceException when calling a method on a dependency that was mock-setup for a different signature.
Assumption
The team assumed that because they had a mock for the service, all its methods were automatically handled — and that missing a Setup would cause an immediate test failure.
Root cause
Moq's Loose mock behavior (default) returns default(T) for any method that hasn't been explicitly set up. The team added a new overload of a method to the interface but forgot to add a corresponding mock Setup. The test exercised a different code path, so the missing Setup never surfaced.
Fix
Switch to Strict mock behavior during development (var mock = new Mock<IService>(MockBehavior.Strict)), which forces every method call to have a setup. Alternatively, run a test coverage analysis that maps unit test execution to mock setups.
Key lesson
  • 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.
Production debug guideSymptom → Action guide for the most common Moq gotchas5 entries
Symptom · 01
Mock returns null or default unexpectedly
Fix
Check if the mock's Setup matches the exact arguments passed at runtime. Use It.IsAny<T>() for flexible matching, and add a .Callback to log what's being passed.
Symptom · 02
Verify throws MockException: 'Expected invocation on the mock N times, but was never performed'
Fix
Confirm the code path under test actually reaches that method call. Use .Callback to trace. Also check if the mock is being passed to the right dependency instance.
Symptom · 03
Test passes but production code fails with NullReferenceException on the same code path
Fix
Your mock likely returns a null object when the real dependency returns something else. Use MockBehavior.Strict or add .Returns() for all overloads. Also consider using DefaultValue.Mock for recursive mocks.
Symptom · 04
Callback or Returns are not invoked
Fix
Verify that the method call matches the Setup exactly, including generic type parameters and ref/out parameters. Use Setup(x => x.Method(It.IsAny<int>(), ref It.Ref<int>.IsAny)).Callback(...) for ref parameters.
Symptom · 05
Async method mock returns Task but test hangs or fails with 'Cannot await Mock<T>'
Fix
For async methods, always use .ReturnsAsync(value) not .Returns(Task.FromResult(value)). Actually both work, but ReturnsAsync is cleaner. Ensure the async target method is actually awaited in the SUT.
★ Quick Debug Cheat Sheet — Moq in ProductionRapid-fire commands and fixes for the most common Moq debugging scenarios. No theory, just what to type and where to look.
Mock returns null unexpectedly
Immediate action
Check MockBehavior — default Loose allows null returns. Switch to Strict in test config.
Commands
var mock = new Mock<IService>(MockBehavior.Strict);
mock.SetupAllProperties(); // for property stubs
Fix now
Add Setup for that exact method signature, or use It.IsAny<T>() for flexible matching.
Verify fails: 'Expected invocation 1 times, but was 0'+
Immediate action
Add .Callback((args) => Console.WriteLine($"Called with {args}")); to the Setup to trace calls.
Commands
mock.Setup(x => x.Method(It.IsAny<int>())).Callback<int>(arg => Console.WriteLine(arg));
mock.Verify(x => x.Method(It.Is<int>(i => i > 0)), Times.Once);
Fix now
Use It.Is<> with a predicate if you only want to verify specific argument values.
Async mock not awaited properly — test passes but production blows up+
Immediate action
Verify the SUT actually awaits the mock method. A missing await means a Task is never completed.
Commands
// In SUT: await dependency.DoSomethingAsync()
// In test: mock.Setup(x => x.DoSomethingAsync()).ReturnsAsync(new Result());
Fix now
If the SUT doesn't await, the mock's ReturnsAsync may never be observed. Add a synchronous overload or ensure the caller awaits.
SetupSequence not producing expected values on later calls+
Immediate action
Check that the test calls the method exactly as many times as the sequence defines. Extra calls will throw if no default.
Commands
mock.SetupSequence(x => x.GetState()).Returns('A').Returns('B').Returns('C');
// Call three times — each returns next in sequence
Fix now
To handle variable number of calls, combine with Callback to track count and conditionally return values.
FeatureWhen to UseRisk if Overused
MockBehavior.LooseQuick prototyping, rarely used code pathsMissing setups hide production bugs
MockBehavior.StrictCritical interfaces, contract testsBrittle tests, high maintenance
SetupSequenceState machines, read-once streamsOrder-dependent tests fail unexpectedly
CallbackCapturing arguments, complex validationShared state across concurrent tests
DefaultValue.MockDeep dependency chains, no need to customize intermediate mocksDifficult to trace where the real return value came from
⚙ Quick Reference
10 commands from this guide
FileCommand / CodePurpose
TheCodeForgeMoqDemoBasicMock.csnamespace TheCodeForge.MoqDemo;What Is Mocking with Moq in C#?
TheCodeForgeMoqDemoSetupExamples.csnamespace TheCodeForge.MoqDemo;Setting Up Mocks
TheCodeForgeMoqDemoVerificationExamples.csnamespace TheCodeForge.MoqDemo;Verification
TheCodeForgeMoqDemoAdvancedPatterns.csnamespace TheCodeForge.MoqDemo;Advanced Patterns
TheCodeForgeMoqDemoProductionGotchas.csnamespace TheCodeForge.MoqDemo;Production Gotchas
PartialPaymentService.cspublic class PaymentProcessorPartial Mocks
ProtectedMockScenario.cspublic class ReportEngineMocking Internals and Protected Members
MockOfExample.cspublic interface IFooMock.Of() for One-Line Mock Creation
AutoMockExample.csusing AutoFixture;AutoMock with AutoFixture + Moq for Cleaner Tests
MockBehaviorExample.cspublic interface IFoo { int GetValue(); void DoSomething(); }MockBehavior.Strict vs Loose

Key takeaways

1
Moq creates proxy implementations
Setup defines behavior, Verify asserts invocations.
2
Strict mode catches missing setups early; Loose mode hides them until production crashes.
3
Use Callback for side effects, SetupSequence for ordered returns, and DefaultValue.Mock for deep chains.
4
Async mocks require the SUT to actually await
otherwise the ReturnsAsync never fires.
5
Inject abstractions for time, random, and other static dependencies
Moq can't mock statics.
6
Run test suite under both Loose and Strict during CI to uncover discrepancies.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the difference between MockBehavior.Loose and MockBehavior.Stric...
Q02SENIOR
How do you mock a method that takes an out parameter in Moq? Provide an ...
Q03SENIOR
What is the difference between Verify() and VerifyAll() in Moq? When sho...
Q01 of 03SENIOR

Explain the difference between MockBehavior.Loose and MockBehavior.Strict in Moq. When would you choose one over the other?

ANSWER
Loose (default) returns default values for any method not explicitly set up — no exception is thrown. This speeds up test creation but risks hiding missing setups that would cause NullReferenceException in production. Strict throws a MockException when any unset method is called, forcing the developer to add a Setup. This makes tests safer but more brittle to interface changes. I start with Strict for critical interfaces (e.g., database repositories) and use Loose for logging or utility interfaces where I don't care about exact calls. In CI, I run the test suite with both behaviors at least once to catch discrepancies.
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
What's the difference between Moq and a handwritten fake?
02
Can Moq mock static methods or sealed classes?
03
How do I mock a method that returns Task of IEnumerable but the implementation returns an empty list?
N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
🔥

That's Testing. Mark it forged?

5 min read · try the examples if you haven't

Previous
Unit Testing in C# with xUnit
2 / 7 · Testing
Next
Integration Testing in ASP.NET Core