Skip to content
End To End Tester

Moq

The .NET mocking library in practical detail — setups, argument matchers, verification, loose versus strict behaviour, and what to use instead where it strains.

2 min read · updated 19 September 2026

Moq is the most widely used .NET mocking library. It builds a proxy at runtime that implements an interface (or overrides a virtual member) and records what happens to it.

#Setup

csharp
var gateway = new Mock<IPaymentGateway>();

// Any argument
gateway.Setup(g => g.ChargeAsync(It.IsAny<ChargeRequest>()))
       .ReturnsAsync(ChargeResult.Succeeded("pi_1"));

// A specific one
gateway.Setup(g => g.ChargeAsync(It.Is<ChargeRequest>(r => r.Cents == 4_000)))
       .ReturnsAsync(ChargeResult.Succeeded("pi_exact"));

// Computed from the input
gateway.Setup(g => g.ChargeAsync(It.IsAny<ChargeRequest>()))
       .ReturnsAsync((ChargeRequest r) => ChargeResult.Succeeded($"pi_{r.Cents}"));

// Throwing
gateway.Setup(g => g.ChargeAsync(It.IsAny<ChargeRequest>()))
       .ThrowsAsync(new RateLimitException(retryAfter: TimeSpan.FromMinutes(1)));

// A sequence — first call fails, second succeeds. The way to test retries.
gateway.SetupSequence(g => g.ChargeAsync(It.IsAny<ChargeRequest>()))
       .ThrowsAsync(new HttpRequestException("503"))
       .ReturnsAsync(ChargeResult.Succeeded("pi_2"));

SetupSequence is the one people miss most often, and it is how you test retry and backoff logic without a real flaky dependency.

#Verification

csharp
gateway.Verify(g => g.ChargeAsync(It.IsAny<ChargeRequest>()), Times.Once);
gateway.Verify(g => g.RefundAsync(It.IsAny<string>()), Times.Never);
gateway.Verify(g => g.ChargeAsync(It.Is<ChargeRequest>(r => r.Cents == 4_000)), Times.Once);

// Nothing else happened to this mock at all
gateway.VerifyNoOtherCalls();

Better than a complicated matcher: capture the argument and assert on it with your normal assertion library.

csharp
ChargeRequest? captured = null;
gateway.Setup(g => g.ChargeAsync(It.IsAny<ChargeRequest>()))
       .Callback<ChargeRequest>(r => captured = r)
       .ReturnsAsync(ChargeResult.Succeeded("pi_1"));

await checkout.PayAsync(order);

Assert.NotNull(captured);
Assert.Equal(4_000, captured!.Cents);
Assert.StartsWith("ORD-", captured.Reference);

The failure message then names the field that was wrong, rather than saying "expected invocation on the mock at least once, but was never performed".

#Loose versus strict

csharp
// Loose (default): unconfigured calls return default(T) and are ignored.
var loose = new Mock<IPaymentGateway>();

// Strict: any call you did not configure fails the test.
var strict = new Mock<IPaymentGateway>(MockBehavior.Strict);

Loose by default. A strict mock turns every new collaboration into a breaking change across every existing test, which is a maintenance tax that buys very little.

Strict earns its place when the set of interactions is the rule — "we must call fraud-check exactly once, before capture, and never after a decline". That is a genuine business invariant and worth pinning precisely.

#Properties, protected members, recursion

csharp
// Properties
var options = new Mock<IOptions<BillingOptions>>();
options.SetupGet(o => o.Value).Returns(new BillingOptions { Currency = "GBP" });

// Let the mock track property writes like a real object
gateway.SetupProperty(g => g.Timeout, TimeSpan.FromSeconds(5));

// Auto-mock the whole chain: a.B.C.D() without stubbing each level
var deep = new Mock<IThing> { DefaultValue = DefaultValue.Mock };

DefaultValue.Mock is convenient and worth being suspicious of: needing a four-level chain stubbed usually means a Law-of-Demeter problem in the production code.

#What Moq cannot do

Moq generates a subclass at runtime, so it can substitute:

  • interfaces
  • virtual and abstract members

and cannot substitute:

  • sealed classes
  • non-virtual methods
  • static methods
  • extension methods (they are static)

When you hit that wall, the correct move is nearly always to define an interface you own and adapt the concrete type behind it:

csharp
// Not mockable: a sealed SDK client
public sealed class StripeClient { public Task<Charge> ChargeAsync(...) { } }

// Mockable: your own seam, with the SDK behind it
public interface IPaymentGateway { Task<ChargeResult> ChargeAsync(ChargeRequest request); }

public sealed class StripePaymentGateway(StripeClient client) : IPaymentGateway
{
    public async Task<ChargeResult> ChargeAsync(ChargeRequest request) =>
        Map(await client.ChargeAsync(Map(request)));       // the only untested line
}

That adapter is then covered by an integration test against the real SDK, and everything above it is unit-testable.

#Alternatives

NSubstitute is the main one, and many teams prefer its syntax:

csharp
var gateway = Substitute.For<IPaymentGateway>();
gateway.ChargeAsync(Arg.Any<ChargeRequest>()).Returns(ChargeResult.Succeeded("pi_1"));
await gateway.Received(1).ChargeAsync(Arg.Is<ChargeRequest>(r => r.Cents == 4_000));

A hand-written fake is frequently better than either, especially for a dependency doubled in twenty tests. It has real behaviour, a real assertion surface, and no expectation syntax to read — see test doubles.

#The licensing note

Moq briefly shipped a dependency that collected developer email addresses from local git config (version 4.20.0, August 2023). It was removed in 4.20.2 after significant objection, but it prompted a wave of migrations to NSubstitute. Worth knowing if you are choosing today; not a reason to migrate a working suite.

Common questions

Should I use loose or strict mocks?
Loose, almost always. A strict mock fails on any call you did not configure, so every new collaboration breaks every existing test. Strict is occasionally right when the exact set of interactions is the contract under test — a fraud check that must happen before capture, say.
Why can Moq not mock my class?
Moq works by generating a subclass at runtime, so it can only override members that are virtual, abstract or on an interface. A sealed class or a non-virtual method cannot be substituted, which is usually a signal to depend on an interface you own instead.
What is the difference between Verify and Verifiable?
Verify(expression, Times) asserts one specific interaction at the end of the test. Setup(...).Verifiable() marks a setup as expected, and a bare mock.Verify() then checks all of them. The first is clearer in most tests.

Runnable samples for this page

last test results ↗
  • C#dotnet/Tests.XUnit/tools/moq

Working tests, not fragments — they run in CI on every push to 8exgh/endtoendtester-samples.

Was this page useful?