ASP.NET中PostgreSQL单元测试:如何模拟数据库连接?
Hey there! Let's walk through how to tackle testing your DocumentsRepository which depends on IConfiguration for database connections. The goal here is to isolate your repository from real external dependencies (like a live PostgreSQL instance) so your tests are fast, reliable, and don't require a running database. Here are a few practical approaches:
1. Mock IConfiguration for Pure Unit Tests
If you're testing logic in your repository that doesn't actually hit the database (like parameter validation or simple data transformations), you can just mock the IConfiguration interface to provide a dummy connection string. We'll use Moq (a popular mocking library) for this:
First, install the Moq package via NuGet:
dotnet add package Moq
Then write your test class:
using Moq; using Microsoft.Extensions.Configuration; using Xunit; public class DocumentsRepositoryTests { [Fact] public void ValidateDocument_ShouldReturnFalse_WhenTitleIsEmpty() { // Create a mock IConfiguration instance var mockConfig = new Mock<IConfiguration>(); // Setup the mock to return a fake connection string (match the key your repo uses) mockConfig.Setup(config => config["ConnectionStrings:PostgresDb"]) .Returns("Host=localhost;Database=FakeDb;Username=test;Password=test"); // Initialize your repository with the mocked config var repo = new DocumentsRepository(mockConfig.Object); // Test your logic var invalidDoc = new Documents { Title = "" }; var result = repo.ValidateDocument(invalidDoc); // Assert the outcome Assert.False(result); } }
This approach is great for isolating individual methods without touching a database. It's fast and focused on testing business logic rather than database interactions.
2. Use Npgsql's In-Memory Database for Integration Tests
If you need to test actual database CRUD operations (like adding/retrieving documents) but don't want to spin up a real PostgreSQL server, Npgsql supports an in-memory mode that mimics a real database. This is perfect for integration tests that validate your EF Core mappings and query logic.
First, install the required package:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
Here's how to set up your test:
using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Xunit; public class DocumentsRepositoryIntegrationTests { [Fact] public async Task AddDocument_ShouldPersistToDatabase() { // Configure DbContext to use Npgsql's in-memory mode var dbOptions = new DbContextOptionsBuilder<YourDbContext>() .UseNpgsql("Host=localhost;Database=TestDb;Username=test;Password=test;Mode=Memory;Cache=Shared") .Options; // Create a temporary DbContext and initialize the database schema using var context = new YourDbContext(dbOptions); await context.Database.EnsureCreatedAsync(); // Build an in-memory IConfiguration with the test connection string var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string> { {"ConnectionStrings:PostgresDb", "Host=localhost;Database=TestDb;Username=test;Password=test;Mode=Memory;Cache=Shared"} }) .Build(); // Initialize the repository var repo = new DocumentsRepository(config); // Perform the test operation var testDoc = new Documents { Title = "My Test Document", Content = "Sample content" }; await repo.AddAsync(testDoc); // Verify the document was saved var savedDoc = await context.Documents.FirstOrDefaultAsync(d => d.Id == testDoc.Id); Assert.NotNull(savedDoc); Assert.Equal("My Test Document", savedDoc.Title); } }
The Mode=Memory;Cache=Shared flags ensure multiple DbContext instances share the same in-memory database. This lets you test real database interactions without any external dependencies.
3. Use Testcontainers for Real PostgreSQL Testing
If you need to test against a fully functional PostgreSQL instance (e.g., to validate stored procedures, triggers, or specific PostgreSQL features), Testcontainers is the way to go. It spins up a temporary PostgreSQL container for your tests and cleans it up automatically when done.
First, install the Testcontainers package for PostgreSQL:
dotnet add package Testcontainers.PostgreSQL
Here's a test class using Testcontainers:
using Testcontainers.PostgreSql; using Microsoft.Extensions.Configuration; using Xunit; public class DocumentsRepositoryContainerTests : IAsyncLifetime { private readonly PostgreSqlContainer _postgresContainer; private IConfiguration _testConfig; public DocumentsRepositoryContainerTests() { // Initialize a PostgreSQL container with the latest image _postgresContainer = new PostgreSqlBuilder() .WithImage("postgres:latest") .WithDatabase("TestDb") .WithUsername("testuser") .WithPassword("testpass") .Build(); } public async Task InitializeAsync() { // Start the container and get the connection string await _postgresContainer.StartAsync(); var connectionString = _postgresContainer.GetConnectionString(); // Build the configuration with the real container connection string _testConfig = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string> { {"ConnectionStrings:PostgresDb", connectionString} }) .Build(); // Optional: Run database migrations to set up schema // using var context = new YourDbContext(_testConfig); // await context.Database.MigrateAsync(); } public async Task DisposeAsync() { // Stop and destroy the container after tests await _postgresContainer.StopAsync(); } [Fact] public async Task GetDocumentById_ShouldReturnCorrectDocument() { var repo = new DocumentsRepository(_testConfig); // Add a test document var testDoc = new Documents { Title = "Container Test Doc" }; await repo.AddAsync(testDoc); // Retrieve it back var retrievedDoc = await repo.GetByIdAsync(testDoc.Id); // Assert it matches Assert.NotNull(retrievedDoc); Assert.Equal("Container Test Doc", retrievedDoc.Title); } }
This approach gives you the most realistic testing environment without the hassle of managing a dedicated test database.
Which Approach Should You Choose?
- Mock
IConfiguration: For fast, isolated unit tests of non-database logic. - Npgsql In-Memory: For integration tests of EF Core mappings and basic CRUD.
- Testcontainers: For testing PostgreSQL-specific features or complex database interactions.
内容的提问来源于stack exchange,提问作者Travis Bincle

