In this lesson, you will learn how to focus your unit tests on protecting the business invariants managed by your Domain-Driven Design (DDD) aggregate roots.
The Aggregate as a Boundary
In DDD, an Aggregate Root is the gatekeeper of your domain. Its primary responsibility is not just holding data, but ensuring that the aggregate remains in a valid state at all times. When we test an aggregate, we are not testing persistence (that belongs in our infrastructure tests); we are testing that the aggregate cannot enter an invalid state regardless of what methods are called on it.
Testing Invariants, Not Properties
Avoid writing tests that verify simple setters or getters. Instead, structure your tests around the business rules (invariants) that define the aggregate’s lifecycle.
Consider an Order aggregate that must not allow line items to be added if the order is already "Shipped":
[Fact]
public void AddItem_ShouldThrowException_WhenOrderIsAlreadyShipped()
{
// Arrange
var order = new Order(OrderId.New(), CustomerId.New());
order.Ship(); // Transition to Shipped state
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
order.AddItem(new ProductId(1), 10.00m));
Assert.Equal("Cannot modify a shipped order.", exception.Message);
}
Why This Matters
When you test invariants, your tests become a living specification of your business rules. If you find yourself needing to mock dependencies (like a repository) to test an aggregate method, it is a strong signal that the logic belongs elsewhere—or that your aggregate is taking on too much responsibility.
The goal is to keep the aggregate pure. By keeping your test suite focused on the "how" and "why" of state changes, you ensure that even if the underlying database technology or the application service layer changes, your core business rules remain robust and verified.
Key Takeaway: Unit tests for aggregates should focus exclusively on asserting that domain invariants are upheld through state transitions, treating the aggregate as a self-contained boundary that denies invalid operations.