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
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
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.
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
// 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
// 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
virtualandabstractmembers
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:
// 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:
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?
Related topics
- Mocking FrameworksMoq, NSubstitute, Mockito, unittest.mock, Sinon and the rest — what each ecosystem's mocking library does well, and the failure modes they share.
- Test DoublesDummy, stub, spy, mock and fake — Meszaros's five kinds of test double, what each is for, and why using the words precisely makes code reviews shorter.
- xUnit.netThe .NET runner with a fresh instance per test — Facts, Theories, fixtures, parallelism and the design opinions baked into it.
- Writing Tests in C#The .NET testing stack — xUnit, Moq or NSubstitute, WebApplicationFactory, Testcontainers — and the dependency injection story that makes it the most testable of the four.
- Unit Testing with MocksThe solitarist approach — isolate the unit behind test doubles. What it buys, the coupling it creates, and the three rules that keep a mock-heavy suite maintainable.
- NUnitThe longest-serving .NET test framework — its constraint-based assertion model, attribute set, and the shared-instance behaviour you have to work with.