Integration Testing C# with Docker: A Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Integration Testing C# with Docker: How to Get Rid of Environment Problems



Every C# developer has encountered this situation: tests pass on the local machine but fail on the CI server. The reason is most often differences in the environment: database version, Redis settings, missing test services. Integration testing C# with Docker solves this problem once and for all. In this article, we will look at how to set up an isolated test environment using containers, write real integration tests, and integrate them into a CI/CD pipeline.



Why Docker is the Perfect Tool for C# Integration Tests



Integration testing checks the interaction between system components: application + database, API + cache, microservices with each other. Without Docker, you would have to:

  • Install PostgreSQL, SQL Server, Redis on every developer machine
  • Configure the CI server with the same versions
  • Clean data between test runs

Docker solves all these tasks. You describe the infrastructure in code (Dockerfile, docker-compose.yml), and it deploys identically on any system. For C# (.NET) this is especially relevant since Microsoft actively supports containerization: official .NET SDK images, SQL Server in a Linux container, integration with Azure DevOps.



Project Setup: Testcontainers + xUnit



For integration testing C# with Docker, we will use the Testcontainers library (available on NuGet). It automatically manages the container lifecycle: starts before tests, stops after completion.



Step 1. Installing Packages



Create a new project for tests or open an existing one. Add NuGet packages:

dotnet add package Testcontainersdotnet add package Testcontainers.PostgreSqldotnet add package xunitdotnet add package FluentAssertions


Step 2. Base Class for Tests with Containers



Let's create an abstract class that will spin up PostgreSQL before running tests:

using Testcontainers.PostgreSql;using Xunit;

public abstract class IntegrationTestBase : IAsyncLifetime{ private readonly PostgreSqlContainer _postgresContainer;

protected string ConnectionString => _postgresContainer.GetConnectionString();

protected IntegrationTestBase() { _postgresContainer = new PostgreSqlBuilder() .WithImage("postgres:15-alpine") .WithDatabase("testdb") .WithUsername("test") .WithPassword("test123") .Build(); }

public async Task InitializeAsync() { await _postgresContainer.StartAsync(); }

public async Task DisposeAsync() { await _postgresContainer.StopAsync(); }}

The class implements IAsyncLifetime from xUnit — the container will start before the first test and stop after the last one. The GetConnectionString() method returns the dynamic port that Docker assigned to the container.



Step 3. Writing an Integration Test



Now let's write a test that checks the repository's work with a real database:

public class UserRepositoryTests : IntegrationTestBase{    [Fact]    public async Task CreateUser_Should_SaveToDatabase()    {        // Arrange        await using var connection = new NpgsqlConnection(ConnectionString);        await connection.OpenAsync();                var repository = new UserRepository(connection);        var user = new User { Name = "Ivan", Email = "ivan@example.com" };

// Act var userId = await repository.CreateAsync(user);

// Assert userId.Should().BeGreaterThan(0); var savedUser = await repository.GetByIdAsync(userId); savedUser.Should().NotBeNull(); savedUser.Name.Sho

Blogs

Book Recommendations