Easy📖Теория2 min

Шаг 20: Unit-тесты

Написание unit-тестов для Domain Layer: MoneyTests и OrderTests с xUnit, FluentAssertions, паттерн Arrange-Act-Assert

Шаг 20: Unit-тесты

Тесты Domain Layer -- самые быстрые и надёжные. Они не требуют БД, HTTP или DI.

MoneyTests.cs

В проекте OrderManagement.Domain.Tests:

using FluentAssertions;
using OrderManagement.Domain.Exceptions;
using OrderManagement.Domain.ValueObjects;

namespace OrderManagement.Domain.Tests;

public class MoneyTests
{
    [Fact]
    public void Constructor_ValidAmount_ShouldCreate()
    {
        var money = new Money(100, "RUB");
        money.Amount.Should().Be(100);
        money.Currency.Should().Be("RUB");
    }

    [Fact]
    public void Constructor_Negative_ShouldThrow()
    {
        var act = () => new Money(-1, "RUB");
        act.Should().Throw<DomainException>();
    }

    [Theory]
    [InlineData(100, 50, 150)]
    [InlineData(0, 0, 0)]
    [InlineData(999.99, 0.01, 1000)]
    public void Add_SameCurrency_ShouldReturnSum(
        decimal a, decimal b, decimal expected)
    {
        var result = new Money(a, "RUB").Add(new Money(b, "RUB"));
        result.Amount.Should().Be(expected);
    }

    [Fact]
    public void Add_DifferentCurrency_ShouldThrow()
    {
        var act = () => new Money(100, "RUB")
            .Add(new Money(50, "USD"));
        act.Should().Throw<DomainException>();
    }

    [Fact]
    public void Equality_SameValues_ShouldBeEqual()
    {
        var a = new Money(100, "RUB");
        var b = new Money(100, "RUB");
        a.Should().Be(b);
        (a == b).Should().BeTrue();
    }
}

OrderTests.cs

using FluentAssertions;
using OrderManagement.Domain.Entities;
using OrderManagement.Domain.Enums;
using OrderManagement.Domain.Events;
using OrderManagement.Domain.Exceptions;
using OrderManagement.Domain.ValueObjects;

namespace OrderManagement.Domain.Tests;

public class OrderTests
{
    private static ShippingAddress CreateAddress() =>
        new("Main St 1", "Moscow", "101000", "Russia");

    [Fact]
    public void Create_ShouldReturnDraftOrder()
    {
        var order = Order.Create("cust-001", CreateAddress());
        order.Status.Should().Be(OrderStatus.Draft);
        order.Items.Should().BeEmpty();
        order.DomainEvents.Should().ContainSingle()
            .Which.Should().BeOfType<OrderCreatedEvent>();
    }

    [Fact]
    public void AddItem_ShouldRecalculateTotal()
    {
        var order = Order.Create("cust-001", CreateAddress());
        order.AddItem("p1", "Laptop", new Money(75000, "RUB"), 2);
        order.Items.Should().HaveCount(1);
        order.TotalAmount.Amount.Should().Be(150000);
    }

    [Fact]
    public void Confirm_EmptyOrder_ShouldThrow()
    {
        var order = Order.Create("cust-001", CreateAddress());
        var act = () => order.Confirm();
        act.Should().Throw<DomainException>();
    }

    [Fact]
    public void Confirm_WithItems_ShouldChangeStatus()
    {
        var order = Order.Create("cust-001", CreateAddress());
        order.AddItem("p1", "Laptop", new Money(75000, "RUB"), 1);
        order.Confirm();
        order.Status.Should().Be(OrderStatus.Confirmed);
    }

    [Fact]
    public void AddItem_ToConfirmedOrder_ShouldThrow()
    {
        var order = Order.Create("cust-001", CreateAddress());
        order.AddItem("p1", "Laptop", new Money(75000, "RUB"), 1);
        order.Confirm();
        var act = () => order.AddItem(
            "p2", "Mouse", new Money(1500, "RUB"), 1);
        act.Should().Throw<DomainException>();
    }
}

Запуск

dotnet test tests/OrderManagement.Domain.Tests

Проверь себя

🧪

Что такое паттерн Arrange-Act-Assert?

🧪

Что делает Should().ContainSingle().Which.Should().BeOfType<T>()?

🧪

Чем Theory с InlineData отличается от Fact?