You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET中PostgreSQL单元测试:如何模拟数据库连接?

How to Mock Database Connections for .NET Core Unit Tests with 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:02:36