Moq Tutorial: Complete Mocking Guide for .NET and C#

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 Moq

Moq 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:

  1. new Mock<IWeatherService>() — create the mock wrapper
  2. .Setup(...).Returns(...) — define what it returns
  3. mockWeather.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 args

Callbacks: 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 bool

Strict

var mock = new Mock<IService>(MockBehavior.Strict);
// Any call without a matching Setup throws MockException
// Use when you want to detect unexpected dependency usage

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

Read more