Moq Tutorial: Complete Mocking Guide for .NET and C#
Moq is the most popular mocking library for .NET. It lets you replace real dependencies (databases, email services, HTTP clients) with controlled fakes in unit tests. This guide covers Setup, Verify, argument matchers, callbacks, async mocks, and common patterns for writing isolated C# tests.
Key Takeaways
Mock<T>.Setup() defines what the mock returns. Mock<T>.Verify() asserts it was called. These two methods cover 90% of mocking scenarios. Understand them first.
It.IsAny<T>() matches any argument. It.Is<T>(pred) matches with a predicate. Use matchers when you care about the shape of the argument but not the exact value.
.Object gives you the mock instance to inject. The Mock<T> wrapper is for setup/verification. Pass mock.Object to the system under test.
Verify by default only checks calls you explicitly assert. Moq doesn't fail on unexpected calls unless you use MockBehavior.Strict. Use MockBehavior.Strict when you need to confirm no extra calls were made.
SetupSequence for calls that return different values each time. First call returns one thing, second call returns another. Useful for retry logic and polling tests.
What Is Moq?
Moq (pronounced "Mock-You" or "Moque") is the most widely used mocking framework for .NET. It uses C# expression trees and lambda syntax to define mock behavior at compile time, which means your mocks refactor safely with your code.
Moq replaces real dependencies with fakes that:
- Return values you control
- Record the calls they receive
- Throw exceptions on demand
- Call back into your test logic
Without mocking, unit tests require real databases, HTTP servers, and email services. With Moq, each test controls its own fake environment.
Installing Moq
dotnet add package MoqMoq works with xUnit, NUnit, and MSTest.
Your First Mock
using Moq;
using Xunit;
public interface IWeatherService
{
string GetForecast(string city);
}
public class TravelPlannerTests
{
[Fact]
public void SuggestActivity_RainyWeather_SuggestsIndoorActivity()
{
// Arrange: create mock and define behavior
var mockWeather = new Mock<IWeatherService>();
mockWeather.Setup(w => w.GetForecast("London")).Returns("Rainy");
var planner = new TravelPlanner(mockWeather.Object); // inject mock
// Act
var suggestion = planner.SuggestActivity("London");
// Assert
Assert.Equal("Museum visit", suggestion);
}
}Three key steps:
new Mock<IWeatherService>()— create the mock wrapper.Setup(...).Returns(...)— define what it returnsmockWeather.Object— get the instance to inject
Setup: Controlling Return Values
Returns
mockRepo.Setup(r => r.GetById(1)).Returns(new Product { Id = 1, Name = "Widget" });ReturnsAsync (for Task-returning methods)
mockRepo.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(new Product { Id = 1 });Throws
mockRepo.Setup(r => r.GetById(-1)).Throws<ArgumentException>();
// Or with a message
mockRepo.Setup(r => r.GetById(-1))
.Throws(new ArgumentException("ID must be positive", "id"));ThrowsAsync
mockClient.Setup(c => c.PostAsync(It.IsAny<string>(), It.IsAny<HttpContent>()))
.ThrowsAsync(new HttpRequestException("Connection refused"));Returns with Callback (dynamic value)
mockRepo.Setup(r => r.GetById(It.IsAny<int>()))
.Returns((int id) => new Product { Id = id, Name = $"Product {id}" });Property Setup
var mockConfig = new Mock<IConfiguration>();
mockConfig.Setup(c => c.MaxRetries).Returns(3);
mockConfig.Setup(c => c.BaseUrl).Returns("https://api.example.com");Argument Matchers
Matchers let you match arguments by value, type, or predicate:
// Any value of the type
mockService.Setup(s => s.Send(It.IsAny<string>(), It.IsAny<int>()))
.Returns(true);
// Specific value
mockService.Setup(s => s.GetUser(42)).Returns(adminUser);
// Predicate-based matching
mockService.Setup(s => s.Process(It.Is<Order>(o => o.Total > 100)))
.Returns("high-value-order");
// Range
mockService.Setup(s => s.GetTier(It.IsInRange(1, 5, Range.Inclusive)))
.Returns("basic");
// Regex on strings
mockService.Setup(s => s.ValidateEmail(It.IsRegex(@"^\S+@\S+\.\S+$")))
.Returns(true);
// Not null
mockService.Setup(s => s.Process(It.IsNotNull<Order>()))
.Returns("processed");Verify: Asserting Calls Were Made
Basic Verify
// Was called exactly once?
mockEmail.Verify(e => e.Send(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
// Was called at least once?
mockEmail.Verify(e => e.Send(It.IsAny<string>(), It.IsAny<string>()), Times.AtLeastOnce);
// Was never called?
mockEmail.Verify(e => e.Send(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
// Called exactly N times?
mockCache.Verify(c => c.Set(It.IsAny<string>(), It.IsAny<object>()), Times.Exactly(3));
// Called between N and M times?
mockLogger.Verify(l => l.Log(It.IsAny<string>()), Times.Between(1, 5, Range.Inclusive));Verify with Specific Arguments
// Verify sent to specific email with specific subject
mockEmail.Verify(
e => e.Send(
"admin@example.com",
It.Is<string>(s => s.Contains("Order Confirmed"))
),
Times.Once
);VerifyAll
// Verifies ALL setups were called at least once
mockRepo.VerifyAll();VerifyNoOtherCalls
// Verifies no calls beyond what you've explicitly verified
mockEmail.Verify(e => e.Send("user@example.com", It.IsAny<string>()), Times.Once);
mockEmail.VerifyNoOtherCalls(); // fails if Send was called with other argsCallbacks: Capturing Arguments
string capturedSubject = null;
mockEmail.Setup(e => e.Send(It.IsAny<string>(), It.IsAny<string>()))
.Callback<string, string>((to, subject) =>
{
capturedSubject = subject;
});
service.RegisterUser(new User { Email = "user@example.com" });
Assert.Contains("Welcome", capturedSubject);Callbacks are useful for asserting the content of complex objects passed to mocks.
Sequential Setups
For methods called multiple times that should return different values:
var mockRetryService = new Mock<IHttpService>();
mockRetryService
.SetupSequence(s => s.Get("/api/status"))
.Throws<HttpRequestException>() // First call fails
.Throws<HttpRequestException>() // Second call fails
.Returns("OK"); // Third call succeeds
var retryClient = new RetryClient(mockRetryService.Object, maxRetries: 3);
var result = retryClient.GetWithRetry("/api/status");
Assert.Equal("OK", result);MockBehavior: Strict vs Loose
Loose (Default)
var mock = new Mock<IService>(); // MockBehavior.Loose
// Unmocked methods return default(T) — null for objects, 0 for ints, false for boolStrict
var mock = new Mock<IService>(MockBehavior.Strict);
// Any call without a matching Setup throws MockException
// Use when you want to detect unexpected dependency usageStrict mocks are useful when you want to ensure your code doesn't make surprise calls to dependencies. Loose mocks are more convenient for quick tests.
Async Testing
[Fact]
public async Task ProcessOrder_ValidOrder_ReturnsOrderId()
{
var mockRepo = new Mock<IOrderRepository>();
var mockEmail = new Mock<IEmailService>();
mockRepo.Setup(r => r.SaveAsync(It.IsAny<Order>()))
.ReturnsAsync(42); // returns Task<int>
mockEmail.Setup(e => e.SendConfirmationAsync(It.IsAny<string>(), 42))
.Returns(Task.CompletedTask); // returns Task (void async)
var service = new OrderService(mockRepo.Object, mockEmail.Object);
var orderId = await service.ProcessOrderAsync(new Order { Total = 150m });
Assert.Equal(42, orderId);
mockEmail.Verify(e => e.SendConfirmationAsync(It.IsAny<string>(), 42), Times.Once);
}Mocking Properties
var mockConfig = new Mock<IAppConfig>();
// Read-only property
mockConfig.Setup(c => c.MaxPageSize).Returns(50);
// Property that can be set and read back
mockConfig.SetupProperty(c => c.CurrentUser, "admin");
mockConfig.Object.CurrentUser = "guest";
Assert.Equal("guest", mockConfig.Object.CurrentUser);
// Setup all properties to track changes
mockConfig.SetupAllProperties();Raising Events
var mockMessageBus = new Mock<IMessageBus>();
var handler = new OrderEventHandler(mockMessageBus.Object);
// Raise the event
mockMessageBus.Raise(b => b.MessageReceived += null, new MessageEventArgs
{
Type = "OrderPlaced",
Payload = @"{""orderId"": 99}"
});
// Verify handler reacted
Assert.True(handler.LastProcessedOrderId == 99);Common Patterns
Builder Pattern for Complex Mocks
public class MockUserRepositoryBuilder
{
private readonly Mock<IUserRepository> _mock = new Mock<IUserRepository>();
public MockUserRepositoryBuilder WithUser(int id, string name)
{
_mock.Setup(r => r.GetById(id))
.Returns(new User { Id = id, Name = name });
return this;
}
public MockUserRepositoryBuilder WithNoUsers()
{
_mock.Setup(r => r.GetAll()).Returns(Enumerable.Empty<User>());
return this;
}
public IUserRepository Build() => _mock.Object;
public Mock<IUserRepository> BuildMock() => _mock;
}
// In tests:
var repo = new MockUserRepositoryBuilder()
.WithUser(1, "Alice")
.WithUser(2, "Bob")
.Build();AutoMock with AutoFixture
For large dependency trees, use AutoFixture with Moq auto-mocking:
dotnet add package AutoFixture.AutoMoq[Fact]
public void SomeTest_AutoMocksDependencies()
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
// All constructor dependencies auto-mocked
var service = fixture.Create<OrderService>();
// Get the auto-created mock to set it up or verify
var mockRepo = fixture.Freeze<Mock<IOrderRepository>>();
mockRepo.Setup(r => r.GetById(1)).Returns(new Order { Id = 1 });
var result = service.GetOrder(1);
Assert.NotNull(result);
}Common Mistakes
Forgetting .Object: You pass mock instead of mock.Object — the compiler catches this, but it's a common newbie error.
Verify before the call happens: Don't call Verify before the method under test runs.
Mocking concrete classes without virtual methods: Moq can only mock interfaces and virtual/abstract class members. If you're mocking a concrete class, the methods must be virtual.
// This won't work — GetData is not virtual
var mock = new Mock<DataService>();
mock.Setup(s => s.GetData()).Returns("test"); // No effect
// This works
public class DataService
{
public virtual string GetData() => "real data"; // virtual!
}Using ReturnsAsync for Task (void) methods: Use .Returns(Task.CompletedTask) for Task-returning methods with no return value:
// Wrong
mock.Setup(s => s.DeleteAsync(1)).ReturnsAsync(); // compile error
// Right
mock.Setup(s => s.DeleteAsync(1)).Returns(Task.CompletedTask);Full Example: Service Under Test
// System under test
public class CheckoutService
{
private readonly IProductRepository _products;
private readonly IPaymentGateway _payments;
private readonly IEmailService _email;
public CheckoutService(
IProductRepository products,
IPaymentGateway payments,
IEmailService email)
{
_products = products;
_payments = payments;
_email = email;
}
public async Task<CheckoutResult> CheckoutAsync(CartItem[] items, string customerEmail)
{
var total = items.Sum(i =>
{
var product = _products.GetById(i.ProductId);
return product.Price * i.Quantity;
});
var paymentResult = await _payments.ChargeAsync(customerEmail, total);
if (paymentResult.Success)
{
await _email.SendReceiptAsync(customerEmail, total);
}
return new CheckoutResult { Success = paymentResult.Success, Total = total };
}
}
// Tests
public class CheckoutServiceTests
{
private readonly Mock<IProductRepository> _mockProducts;
private readonly Mock<IPaymentGateway> _mockPayments;
private readonly Mock<IEmailService> _mockEmail;
private readonly CheckoutService _service;
public CheckoutServiceTests()
{
_mockProducts = new Mock<IProductRepository>();
_mockPayments = new Mock<IPaymentGateway>();
_mockEmail = new Mock<IEmailService>();
_service = new CheckoutService(
_mockProducts.Object,
_mockPayments.Object,
_mockEmail.Object
);
}
[Fact]
public async Task Checkout_ValidItems_PaymentSucceeds_SendsReceipt()
{
_mockProducts.Setup(p => p.GetById(1)).Returns(new Product { Id = 1, Price = 50m });
_mockProducts.Setup(p => p.GetById(2)).Returns(new Product { Id = 2, Price = 25m });
_mockPayments.Setup(p => p.ChargeAsync("user@test.com", 75m))
.ReturnsAsync(new PaymentResult { Success = true });
_mockEmail.Setup(e => e.SendReceiptAsync(It.IsAny<string>(), It.IsAny<decimal>()))
.Returns(Task.CompletedTask);
var result = await _service.CheckoutAsync(
new[] { new CartItem { ProductId = 1, Quantity = 1 }, new CartItem { ProductId = 2, Quantity = 1 } },
"user@test.com"
);
Assert.True(result.Success);
Assert.Equal(75m, result.Total);
_mockEmail.Verify(e => e.SendReceiptAsync("user@test.com", 75m), Times.Once);
}
[Fact]
public async Task Checkout_PaymentFails_DoesNotSendReceipt()
{
_mockProducts.Setup(p => p.GetById(It.IsAny<int>()))
.Returns(new Product { Price = 100m });
_mockPayments.Setup(p => p.ChargeAsync(It.IsAny<string>(), It.IsAny<decimal>()))
.ReturnsAsync(new PaymentResult { Success = false });
var result = await _service.CheckoutAsync(
new[] { new CartItem { ProductId = 1, Quantity = 1 } },
"user@test.com"
);
Assert.False(result.Success);
_mockEmail.Verify(
e => e.SendReceiptAsync(It.IsAny<string>(), It.IsAny<decimal>()),
Times.Never
);
}
}Integration with HelpMeTest
Moq handles the unit testing layer — isolated, fast, no infrastructure. For the integration layer, HelpMeTest runs end-to-end Robot Framework + Playwright tests against your real deployed application, catching the gaps that mocked tests can't: broken API contracts, misconfigured environments, and UI regressions.
Running both layers in CI gives you complete coverage: Moq for internal logic verification, HelpMeTest for live application confidence.