testcontainers
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIntegration Testing with TestContainers
使用TestContainers进行集成测试
When to Use This Skill
何时使用该技能
Use this skill when:
- Writing integration tests that need real infrastructure (databases, caches, message queues)
- Testing data access layers against actual databases
- Verifying message queue integrations
- Testing Redis caching behavior
- Avoiding mocks for infrastructure components
- Ensuring tests work against production-like environments
- Testing database migrations and schema changes
适用于以下场景:
- 编写需要真实基础设施(数据库、缓存、消息队列)的集成测试
- 针对实际数据库测试数据访问层
- 验证消息队列集成效果
- 测试Redis缓存行为
- 避免为基础设施组件编写mock
- 确保测试在类生产环境下可正常运行
- 测试数据库迁移和schema变更
Core Principles
核心原则
- Real Infrastructure Over Mocks - Use actual databases/services in containers, not mocks
- Test Isolation - Each test gets fresh containers or fresh data
- Automatic Cleanup - TestContainers handles container lifecycle and cleanup
- Fast Startup - Reuse containers across tests in the same class when appropriate
- CI/CD Compatible - Works seamlessly in Docker-enabled CI environments
- Port Randomization - Containers use random ports to avoid conflicts
- 优先使用真实基础设施而非Mock - 在容器中使用实际的数据库/服务,而非mock
- 测试隔离 - 每个测试都使用全新的容器或全新的数据
- 自动清理 - TestContainers负责处理容器的生命周期和清理工作
- 快速启动 - 合适的情况下可在同一个类的多个测试中复用容器
- 兼容CI/CD - 可在支持Docker的CI环境中无缝运行
- 端口随机化 - 容器使用随机端口避免冲突
Why TestContainers Over Mocks?
为什么选择TestContainers而非Mock?
Problems with Mocking Infrastructure
Mock基础设施的问题
csharp
// BAD: Mocking a database
public class OrderRepositoryTests
{
private readonly Mock<IDbConnection> _mockDb = new();
[Fact]
public async Task GetOrder_ReturnsOrder()
{
// This doesn't test real SQL behavior, constraints, or performance
_mockDb.Setup(db => db.QueryAsync<Order>(It.IsAny<string>()))
.ReturnsAsync(new[] { new Order { Id = 1 } });
var repo = new OrderRepository(_mockDb.Object);
var order = await repo.GetOrderAsync(1);
Assert.NotNull(order);
}
}Problems:
- Doesn't test actual SQL queries
- Misses database constraints, indexes, and performance
- Can give false confidence
- Doesn't catch SQL syntax errors or schema mismatches
csharp
// BAD: Mocking a database
public class OrderRepositoryTests
{
private readonly Mock<IDbConnection> _mockDb = new();
[Fact]
public async Task GetOrder_ReturnsOrder()
{
// This doesn't test real SQL behavior, constraints, or performance
_mockDb.Setup(db => db.QueryAsync<Order>(It.IsAny<string>()))
.ReturnsAsync(new[] { new Order { Id = 1 } });
var repo = new OrderRepository(_mockDb.Object);
var order = await repo.GetOrderAsync(1);
Assert.NotNull(order);
}
}存在的问题:
- 无法测试实际的SQL查询
- 无法发现数据库约束、索引和性能问题
- 可能给出错误的测试信心
- 无法捕获SQL语法错误或schema不匹配问题
Better: TestContainers with Real Database
更优方案:搭配真实数据库使用TestContainers
csharp
// GOOD: Testing against a real database
public class OrderRepositoryTests : IAsyncLifetime
{
private readonly TestcontainersContainer _dbContainer;
private IDbConnection _connection;
public OrderRepositoryTests()
{
_dbContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.WithEnvironment("ACCEPT_EULA", "Y")
.WithEnvironment("SA_PASSWORD", "Your_password123")
.WithPortBinding(1433, true)
.Build();
}
public async Task InitializeAsync()
{
await _dbContainer.StartAsync();
var port = _dbContainer.GetMappedPublicPort(1433);
var connectionString = $"Server=localhost,{port};Database=TestDb;User Id=sa;Password=Your_password123;TrustServerCertificate=true";
_connection = new SqlConnection(connectionString);
await _connection.OpenAsync();
// Run migrations
await RunMigrationsAsync(_connection);
}
public async Task DisposeAsync()
{
await _connection.DisposeAsync();
await _dbContainer.DisposeAsync();
}
[Fact]
public async Task GetOrder_WithRealDatabase_ReturnsOrder()
{
// Arrange: Insert real test data
await _connection.ExecuteAsync(
"INSERT INTO Orders (Id, CustomerId, Total) VALUES (1, 'CUST1', 100.00)");
var repo = new OrderRepository(_connection);
// Act: Execute against real database
var order = await repo.GetOrderAsync(1);
// Assert: Verify actual database behavior
Assert.NotNull(order);
Assert.Equal(1, order.Id);
Assert.Equal("CUST1", order.CustomerId);
Assert.Equal(100.00m, order.Total);
}
}Benefits:
- Tests real SQL queries and database behavior
- Catches constraint violations, index issues, and performance problems
- Verifies migrations work correctly
- Gives true confidence in data access layer
csharp
// GOOD: Testing against a real database
public class OrderRepositoryTests : IAsyncLifetime
{
private readonly TestcontainersContainer _dbContainer;
private IDbConnection _connection;
public OrderRepositoryTests()
{
_dbContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.WithEnvironment("ACCEPT_EULA", "Y")
.WithEnvironment("SA_PASSWORD", "Your_password123")
.WithPortBinding(1433, true)
.Build();
}
public async Task InitializeAsync()
{
await _dbContainer.StartAsync();
var port = _dbContainer.GetMappedPublicPort(1433);
var connectionString = $"Server=localhost,{port};Database=TestDb;User Id=sa;Password=Your_password123;TrustServerCertificate=true";
_connection = new SqlConnection(connectionString);
await _connection.OpenAsync();
// Run migrations
await RunMigrationsAsync(_connection);
}
public async Task DisposeAsync()
{
await _connection.DisposeAsync();
await _dbContainer.DisposeAsync();
}
[Fact]
public async Task GetOrder_WithRealDatabase_ReturnsOrder()
{
// Arrange: Insert real test data
await _connection.ExecuteAsync(
"INSERT INTO Orders (Id, CustomerId, Total) VALUES (1, 'CUST1', 100.00)");
var repo = new OrderRepository(_connection);
// Act: Execute against real database
var order = await repo.GetOrderAsync(1);
// Assert: Verify actual database behavior
Assert.NotNull(order);
Assert.Equal(1, order.Id);
Assert.Equal("CUST1", order.CustomerId);
Assert.Equal(100.00m, order.Total);
}
}优势:
- 测试真实SQL查询和数据库行为
- 可以捕获约束违反、索引问题和性能问题
- 验证迁移是否正常工作
- 让你对数据访问层有真正的信心
Required NuGet Packages
所需NuGet包
xml
<ItemGroup>
<PackageReference Include="Testcontainers" Version="*" />
<PackageReference Include="xunit" Version="*" />
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
<!-- Database-specific packages -->
<PackageReference Include="Microsoft.Data.SqlClient" Version="*" />
<PackageReference Include="Npgsql" Version="*" /> <!-- For PostgreSQL -->
<PackageReference Include="MySqlConnector" Version="*" /> <!-- For MySQL -->
<!-- Other infrastructure -->
<PackageReference Include="StackExchange.Redis" Version="*" /> <!-- For Redis -->
<PackageReference Include="RabbitMQ.Client" Version="*" /> <!-- For RabbitMQ -->
</ItemGroup>xml
<ItemGroup>
<PackageReference Include="Testcontainers" Version="*" />
<PackageReference Include="xunit" Version="*" />
<PackageReference Include="xunit.runner.visualstudio" Version="*" />
<!-- Database-specific packages -->
<PackageReference Include="Microsoft.Data.SqlClient" Version="*" />
<PackageReference Include="Npgsql" Version="*" /> <!-- For PostgreSQL -->
<PackageReference Include="MySqlConnector" Version="*" /> <!-- For MySQL -->
<!-- Other infrastructure -->
<PackageReference Include="StackExchange.Redis" Version="*" /> <!-- For Redis -->
<PackageReference Include="RabbitMQ.Client" Version="*" /> <!-- For RabbitMQ -->
</ItemGroup>Getting Started
入门指南
The Testcontainers library provides a simple API for managing Docker containers in your tests. Each test can spin up the infrastructure it needs, and Testcontainers handles the lifecycle automatically.
Testcontainers库提供了简单的API,用于在测试中管理Docker容器。每个测试都可以启动它需要的基础设施,Testcontainers会自动处理生命周期。
Reference Documentation
参考文档
For detailed patterns and examples, see the reference files:
- Database Containers - SQL Server, PostgreSQL, MySQL, and migration patterns
- Message Broker Containers - RabbitMQ, Kafka, and Service Bus patterns
- Advanced Patterns - Networks, volumes, wait strategies, cleanup, and performance optimization
有关详细模式和示例,请参阅参考文件:
- 数据库容器 - SQL Server、PostgreSQL、MySQL和迁移模式
- 消息代理容器 - RabbitMQ、Kafka和服务总线模式
- 高级模式 - 网络、卷、等待策略、清理和性能优化
Best Practices
最佳实践
- Always Use IAsyncLifetime - Proper async setup and teardown
- Wait for Port Availability - Use to ensure containers are ready
WaitStrategy - Use Random Ports - Let TestContainers assign ports automatically
- Clean Data Between Tests - Either use fresh containers or truncate tables
- Reuse Containers When Possible - Faster than creating new ones for each test
- Test Real Queries - Don't just test mocks; verify actual SQL behavior
- Verify Constraints - Test foreign keys, unique constraints, indexes
- Test Transactions - Verify rollback and commit behavior
- Use Realistic Data - Test with production-like data volumes
- Handle Cleanup - Always dispose containers in
DisposeAsync
- 始终使用IAsyncLifetime - 正确的异步设置和销毁逻辑
- 等待端口可用 - 使用确保容器已准备就绪
WaitStrategy - 使用随机端口 - 让TestContainers自动分配端口
- 测试之间清理数据 - 要么使用全新容器,要么截断数据表
- 尽可能复用容器 - 比为每个测试创建新容器速度更快
- 测试真实查询 - 不要只测试mock;验证实际SQL执行行为
- 验证约束 - 测试外键、唯一约束、索引逻辑
- 测试事务 - 验证回滚和提交行为
- 使用真实数据 - 使用类生产的数据量进行测试
- 做好清理工作 - 始终在中销毁容器
DisposeAsync