Leveraging Moq in .NET 9 for Robust Unit Testing with NUnit
Testing modern .NET applications often requires isolating dependencies, simulating external services, and verifying interactions. Moq…
Leveraging Moq in .NET 9 for Robust Unit Testing with NUnit

Testing modern .NET applications often requires isolating dependencies, simulating external services, and verifying interactions. Moq, combined with NUnit, is a powerful toolset that allows developers to achieve this with minimal boilerplate. In this article, we explore a real-world scenario of notifying external systems when a domain entity changes.
The Problem: Tracking Changes in Domain Entities
Consider a domain entity, Person, which supports snapshots to detect changes:
public class Person : Entity<Guid>, ISnapshotable<Person>
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public DateTime DateOfBirth { get; private set; }
public bool IsActive { get; private set; }
public Person(Guid id, string firstName, string lastName, DateTime dateOfBirth)
: base(id)
{
SetName(firstName, lastName);
SetDateOfBirth(dateOfBirth);
IsActive = true;
}
public void SetName(string firstName, string lastName)
{
if (string.IsNullOrWhiteSpace(firstName)) throw new ArgumentException("First name cannot be empty.");
if (string.IsNullOrWhiteSpace(lastName)) throw new ArgumentException("Last name cannot be empty.");
FirstName = firstName;
LastName = lastName;
}
public Person CreateSnapshot() => new Person(Id, FirstName, LastName, DateOfBirth) { IsActive = this.IsActive };
public bool HasChanges(Person snapshot) =>
FirstName != snapshot.FirstName || LastName != snapshot.LastName || DateOfBirth != snapshot.DateOfBirth || IsActive != snapshot.IsActive;
}
Here, Person supports snapshotting and change detection via ISnapshotable<T>. This pattern is common in domain-driven design (DDD), where entities manage their own state and behaviors.
The Service: Entity Update Notifications
We need a service that:
- Captures a snapshot of the entity.
- Detects changes.
- Notifies an external system if the entity has changed.
public class EntityUpdateNotificationService<T> where T : Entity<Guid>, ISnapshotable<T>
{
private readonly IMessageService _messageService;
private T? _snapshot;
public EntityUpdateNotificationService(IMessageService messageService)
{
_messageService = messageService ?? throw new ArgumentNullException(nameof(messageService));
}
public void CaptureSnapshot(T entity) => _snapshot = entity.CreateSnapshot();
public async Task NotifyIfChangedAsync(T entity, CancellationToken cancellationToken = default)
{
if (_snapshot != null && entity.HasChanges(_snapshot))
{
await _messageService.FireAndForgetAsync(new MessageEnvelope { Data = entity }, cancellationToken);
_snapshot = entity.CreateSnapshot();
}
}
}
The service relies on an IMessageService interface to send notifications. In unit testing, we want to verify this behavior without actually sending messages.
Why Moq?
Moq is a mocking framework for .NET that allows developers to:
- Simulate external dependencies: Replace real services (like databases, APIs, message brokers) with mocks.
- Verify interactions: Ensure methods are called with expected parameters, exact number of times, and even with specific data.
- Simplify async testing: Moq supports asynchronous methods naturally, which is critical in modern .NET applications.
- Maintain readability: Minimal setup and expressive syntax make tests easy to read and maintain.
Instead of building a custom test double or manually tracking calls, Moq does it elegantly in just a few lines.
Testing with NUnit and Moq
[TestFixture]
public class EntityUpdateNotificationServiceTests
{
private Mock<IMessageService> _messageServiceMock = null!;
private EntityUpdateNotificationService<Person> _service = null!;
[SetUp]
public void Setup()
{
_messageServiceMock = new Mock<IMessageService>();
_messageServiceMock
.Setup(x => x.FireAndForgetAsync(It.IsAny<MessageEnvelope>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
_service = new EntityUpdateNotificationService<Person>(_messageServiceMock.Object);
}
private Person CreatePerson(string firstName = "John") =>
new Person(Guid.NewGuid(), firstName, "Doe", new DateTime(1990, 1, 1));
[Test]
public async Task NotifyIfChangedAsync_Should_Do_Nothing_When_Snapshot_Not_Captured()
{
var person = CreatePerson();
await _service.NotifyIfChangedAsync(person);
_messageServiceMock.Verify(x => x.FireAndForgetAsync(It.IsAny<MessageEnvelope>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task NotifyIfChangedAsync_Should_Send_When_Data_Changed()
{
var person = CreatePerson();
_service.CaptureSnapshot(person);
person.SetName("Updated", person.LastName);
await _service.NotifyIfChangedAsync(person);
// Verify that the mock received exactly one call with the correct entity
_messageServiceMock.Verify(
x => x.FireAndForgetAsync(
It.Is<MessageEnvelope>(m => m.Data == person),
It.IsAny<CancellationToken>()),
Times.Once);
}
}
Moq Highlights in These Tests:
**Mock<IMessageService>**: No real messaging system is needed.**It.IsAny<T>()**: Flexible argument matching.**It.Is<T>(predicate)**: Verifies that the right data was sent.**Times.Once/Times.Never**: Verifies call frequency.- Async-friendly: Supports
Taskreturn types without additional plumbing.
This makes the tests robust, fast, and deterministic.
Best Practices with Moq
- Mock behavior, not implementation: Focus on what the service should do, not how it does it.
- Avoid over-mocking: Only mock external dependencies, not the class under test.
- Use
Verifyto ensure interactions: Ensures correct collaboration between components. - Use
Setupfor predictable results: Helps simulate edge cases and errors. - Test cancellation tokens: Moq allows passing and verifying cancellation tokens for async operations.
Conclusion
Using Moq with NUnit in .NET 9:
- Ensures isolated, reliable unit tests.
- Simulates external dependencies without side effects.
- Provides clear verification of interactions.
- Simplifies async testing in modern applications.
This approach is essential in domain-driven designs, where entities manage their own state and services act on them. By combining snapshotting, change detection, and Moq-based testing, developers can build robust, maintainable, and testable applications.
메타데이터
- post_id
- e1b037c5bcfd
- slug
- leveraging-moq-in-net-9-for-robust-unit-testing-with-nunit-e1b037c5bcfd
- url
- https://medium.com/@EdwardCurtin/leveraging-moq-in-net-9-for-robust-unit-testing-with-nunit-e1b037c5bcfd
- canonical_url
- https://medium.com/@EdwardCurtin/leveraging-moq-in-net-9-for-robust-unit-testing-with-nunit-e1b037c5bcfd
- author_url
- https://medium.com/@EdwardCurtin
- status
- ok
- fetched_at
- 2026-07-13 06:23:13