| name | dotnet-integration-test |
| description | Write unit and integration tests using xUnit, Moq, FluentAssertions, and Testcontainers with real PostgreSQL, Redis, and RabbitMQ. Use when user says "write tests", "add test", "integration test", "unit test", "testcontainers", "test coverage", or asks to verify functionality. Do NOT use for production code. |
| metadata | {"author":"Claude-DotNet-Ultimate","version":"1.0.0","category":"testing"} |
Testing Patterns
Project Structure
tests/
├── Unit/ # Fast, isolated tests
│ ├── Domain/ # Entity/ValueObject tests
│ └── Application/ # Handler/Validator tests
└── Integration/ # Testcontainers tests
├── DatabaseTests.cs # EF Core + PostgreSQL
└── MessagingTests.cs # MassTransit + Redis + RabbitMQ
Unit Testing Patterns
Domain Entity Tests
namespace ClaudeDotNetUltimate.Tests.Unit.Domain;
public sealed class OrderTests
{
[Fact]
public void Create_WithValidCustomer_ReturnsOrderWithPendingStatus()
{
var customerId = CustomerId.New();
var address = Address.Create("123 Main St", "City", "ST", "12345", "US");
var order = Order.Create(customerId, address, address, "Test notes");
order.Should().NotBeNull();
order.CustomerId.Should().Be(customerId);
order.Status.Should().Be(OrderStatus.Pending);
order.DomainEvents.Should().ContainSingle()
.Which.Should().BeOfType<OrderCreatedEvent>();
}
[Fact]
public void Confirm_WhenPending_SetsStatusToConfirmed()
{
var order = CreateTestOrder();
var result = order.Confirm();
result.IsSuccess.Should().BeTrue();
order.Status.Should().Be(OrderStatus.Confirmed);
}
[Fact]
public void Confirm_WhenAlreadyCancelled_ReturnsFailure()
{
var order = CreateTestOrder();
order.Cancel("reason");
var result = order.Confirm();
result.IsFailure.Should().BeTrue();
result.Error!.Type.Should().Be(ErrorType.BusinessRuleViolation);
}
private static Order CreateTestOrder()
{
var address = Address.Create("123 Main", "City", "ST", "12345", "US");
return Order.Create(CustomerId.New(), address, address, null);
}
}
Command Handler Tests
public sealed class CreateOrderCommandHandlerTests
{
private readonly Mock<IUnitOfWork> _unitOfWork = new();
private readonly Mock<IMessageBus> _messageBus = new();
private readonly Mock<ILogger<CreateOrderCommandHandler>> _logger = new();
[Fact]
public async Task Handle_WithValidCommand_ReturnsSuccessResult()
{
_unitOfWork.Setup(x => x.SaveChangesWithResultAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Success());
_unitOfWork.Setup(x => x.Orders).Returns(Mock.Of<IOrderRepository>());
var handler = new CreateOrderCommandHandler(
_unitOfWork.Object, _messageBus.Object, _logger.Object);
var command = new CreateOrderCommand();
Result<OrderDto> result = await handler.Handle(command, CancellationToken.None);
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeNull();
}
}
Validator Tests
public sealed class CreateOrderCommandValidatorTests
{
private readonly CreateOrderCommandValidator _validator = new();
[Fact]
public void Validate_WithEmptyCustomerId_ReturnsValidationError()
{
var command = new CreateOrderCommand(Guid.Empty, );
var result = _validator.Validate(command);
result.IsValid.Should().BeFalse();
result.Errors.Should().Contain(e => e.PropertyName == "CustomerId");
}
}
Integration Testing with Testcontainers
PostgreSQL Tests
public sealed class DatabaseTests : IAsyncLifetime
{
private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
.WithImage("postgres:17-alpine")
.WithDatabase("testdb")
.WithUsername("test")
.WithPassword("test")
.Build();
private ApplicationDbContext _context = null!;
public async Task InitializeAsync()
{
await _postgres.StartAsync();
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseNpgsql(_postgres.GetConnectionString())
.Options;
_context = new ApplicationDbContext(options);
await _context.Database.EnsureCreatedAsync();
}
[Fact]
public async Task SaveOrder_PersistsToDatabase()
{
var order = Order.Create(CustomerId.New(), );
_context.Orders.Add(order);
await _context.SaveChangesAsync();
var saved = await _context.Orders.FindAsync(order.Id);
saved.Should().NotBeNull();
saved!.Status.Should().Be(OrderStatus.Pending);
}
public async Task DisposeAsync() => await _postgres.DisposeAsync();
}
MassTransit In-Memory Tests
public sealed class MessagingTests
{
[Fact]
public async Task PublishOrderCreated_ConsumerReceivesEvent()
{
await using var provider = new ServiceCollection()
.AddMassTransitTestHarness(cfg =>
{
cfg.AddConsumer<OrderCreatedConsumer>();
})
.BuildServiceProvider(true);
var harness = provider.GetRequiredService<ITestHarness>();
await harness.Start();
await harness.Bus.Publish(new OrderCreatedEvent());
(await harness.Consumed.Any<OrderCreatedEvent>()).Should().BeTrue();
}
}
Test Commands
dotnet test
dotnet test --project tests/Unit
dotnet test --project tests/Integration
dotnet test --filter "FullyQualifiedName~OrderTests"
dotnet test --collect:"XPlat Code Coverage"
dotnet test -v n
Rules
- Arrange-Act-Assert pattern in every test
- Use FluentAssertions for readable assertions
- Use Moq for mocking interfaces in unit tests
- Use Testcontainers for real infrastructure in integration tests
- Test both success and failure paths
- Test edge cases and domain invariants
- Name tests:
MethodName_Condition_ExpectedResult
- Each test class implements
IAsyncLifetime for setup/teardown when needed