Moq is the most widely used mocking library in the .NET ecosystem. It uses LINQ expressions to set up method behaviour and verify interactions — a design that provides strong IntelliSense support and compile-time safety. Understanding Moq’s core operations (Setup, Returns, Callback, Verify) enables isolated unit tests where dependencies are controlled precisely, allowing tests to verify that the service under test calls the right method with the right arguments and handles the response correctly.
Moq Core Patterns
// ── Setup and Returns ─────────────────────────────────────────────────────
var repoMock = new Mock<IPostsRepository>(MockBehavior.Strict);
// Exact argument match:
repoMock.Setup(r => r.GetBySlugAsync("my-post", default))
.ReturnsAsync(new Post { Id = 1, Slug = "my-post" });
// Any argument match:
repoMock.Setup(r => r.GetBySlugAsync(It.IsAny<string>(), default))
.ReturnsAsync((Post?)null);
// Conditional argument match:
repoMock.Setup(r => r.GetByIdAsync(
It.Is<int>(id => id > 0), default))
.ReturnsAsync(new Post { Id = 42 });
// Property setup:
var userMock = new Mock<ICurrentUserService>();
userMock.SetupGet(u => u.UserId).Returns("user-123");
userMock.SetupGet(u => u.IsAuthenticated).Returns(true);
// ── SetupSequence — different return values on successive calls ────────────
repoMock.SetupSequence(r => r.GetViewCountAsync(1, default))
.ReturnsAsync(100) // first call
.ReturnsAsync(101) // second call
.ThrowsAsync(new Exception("DB error")); // third call
// ── Callback — capture passed arguments ───────────────────────────────────
Post? savedPost = null;
repoMock.Setup(r => r.AddAsync(It.IsAny<Post>(), default))
.Callback<Post, CancellationToken>((post, _) => savedPost = post)
.ReturnsAsync((Post p, CancellationToken _) => { p.Id = 1; return p; });
// After calling the service:
savedPost.Should().NotBeNull();
savedPost!.Slug.Should().Be("expected-slug");
savedPost.AuthorId.Should().Be("user-123");
// ── Verify — confirm interactions ─────────────────────────────────────────
// Verify called exactly once with specific arguments:
repoMock.Verify(r => r.AddAsync(
It.Is<Post>(p => p.Status == "draft"),
default), Times.Once);
// Verify never called (important for error path tests):
repoMock.Verify(r => r.AddAsync(It.IsAny<Post>(), default), Times.Never);
// VerifyAll — checks every Setup was called (MockBehavior.Strict alternative):
repoMock.VerifyAll();
// ── Mock vs Stub vs Fake ──────────────────────────────────────────────────
// STUB: just returns values, we don't verify how it was called
var stubRepo = new Mock<IPostsRepository>();
stubRepo.Setup(r => r.GetBySlugAsync("slug", default))
.ReturnsAsync(new Post());
// No Verify call — we only care about the return value
// MOCK: verifies the interaction occurred
var mockEmailSvc = new Mock<IEmailService>();
// ... run service ...
mockEmailSvc.Verify(e => e.SendPublishedNotificationAsync(
It.Is<string>(email => email.Contains("@")),
It.IsAny<string>(), default), Times.Once);
MockBehavior.Strict throws a MockException for any call to the mock that doesn’t have a corresponding Setup(). This is invaluable for ensuring tests are explicit about every interaction — if the service calls a method you didn’t expect, the test fails immediately rather than silently returning a default value. Use Strict for repository and critical service mocks; use the default Loose for logger or notification mocks where you don’t need exhaustive setup.It.Is<T>(predicate) with a specific property check over It.IsAny<T>(). Verify(r => r.AddAsync(It.Is<Post>(p => p.Slug == "correct-slug"), default), Times.Once) confirms not just that AddAsync was called, but that it was called with the correct post. This catches bugs where the service calls the method with wrong data — which It.IsAny would miss.Verify() makes tests brittle — they break when the implementation changes even if the observable behaviour remains correct. If GetBySlugAsync is refactored to use a cache first, the Verify call that checks the repository was called directly may fail even though the service still returns the correct result. Use Verify primarily for side effects with no observable return value (sending emails, writing audit logs). For operations that return data, assert on the return value instead.Common Mistakes
Mistake 1 — Setup with exact argument that doesn’t match what the service passes (returns null/default)
❌ Wrong — Setup(r => r.GetByIdAsync(1, default)).ReturnsAsync(post); service calls with CancellationToken ct (not default); setup doesn’t match; returns null.
✅ Correct — use It.IsAny<CancellationToken>() for CancellationToken arguments, or default only if the token is literally default.
Mistake 2 — Verifying interactions instead of asserting return values (implementation coupling)
❌ Wrong — Verify(r => r.GetBySlugAsync(slug, default), Times.Once); breaks when caching is added.
✅ Correct — assert on the return value: result.Slug.Should().Be(slug); implementation can change freely.