{"id":18390801,"url":"https://github.com/eifinger/dotnet-testcontainers","last_synced_at":"2025-09-14T20:57:13.208Z","repository":{"id":96180699,"uuid":"579887673","full_name":"eifinger/dotnet-testcontainers","owner":"eifinger","description":"Demo Application which shows how to use a real database during integrationtests for C# Applications.","archived":false,"fork":false,"pushed_at":"2022-12-19T07:31:57.000Z","size":18,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-12T10:52:56.519Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eifinger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-19T07:27:57.000Z","updated_at":"2022-12-19T07:32:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"35c4cd7f-aafa-46a5-9bbb-28da2a31ad97","html_url":"https://github.com/eifinger/dotnet-testcontainers","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/eifinger/dotnet-testcontainers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eifinger%2Fdotnet-testcontainers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eifinger%2Fdotnet-testcontainers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eifinger%2Fdotnet-testcontainers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eifinger%2Fdotnet-testcontainers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eifinger","download_url":"https://codeload.github.com/eifinger/dotnet-testcontainers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eifinger%2Fdotnet-testcontainers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275167464,"owners_count":25416990,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-14T02:00:10.474Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T01:49:13.977Z","updated_at":"2025-09-14T20:57:13.183Z","avatar_url":"https://github.com/eifinger.png","language":"C#","readme":"# Testcontainers Demo\n\nDemo Application which shows how to use a real database during integrationtests for C# Applications.\n\nThis Application uses\n\n* ASP.NET Core\n* EF Core\n* [PostgreSQL](https://hub.docker.com/_/postgres)\n* [Alba](https://jasperfx.github.io/alba/guide/)\n* [Testcontainer](https://www.testcontainers.org/)\n* xUnit\n\n## What are Testcontainers?\n\nTestcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.\n\nhttps://github.com/testcontainers/testcontainers-dotnet is a dotnet version of it.\n\nIt enables you to automatically spin up any docker container e.g. a postgreSQL DB prepare it for tests and automatically destroy it after your tests ran.\n\nThis way you can run your integrationtests against a real db without using in-memory subsitutes or manually setting up a database before running your services.\n\n## How to set up the project\n\n### Clone this repo\n\nYou can just clone this repo and run the unit tests.\n\n### From scratch\n\n#### Create the projects\n\nCreate a new directory `dotnet-testcontainers` by running\n\n````shell\nmkdir dotnet-testcontainers\n````\n\nChange into the directory\n\n````shell\ncd dotnet-testcontainers\n````\n\nInitialize a solution and add a ASP.NET Core API project with the name `Application` to it\n\n````shell\ndotnet new sln\ndotnet new webapi -o Application\ndotnet sln add ./Application/Application.csproj\n````\n\nAdd a xUnit Project with the name `Application.Tests` to it\n\n````shell\ndotnet new xunit -o Application.Tests\ndotnet add .\\Application.Tests\\Application.Tests.csproj reference .\\Application\\Application.csproj\ndotnet sln add .\\Application.Tests\\Application.Tests.csproj\n````\n\n#### Add dependencies\n\nAdd needed libraries to our xUnit Project\n\n````shell\ncd .\\Application.Tests\ndotnet add package Alba --version 6.1.0\ndotnet add package Testcontainers --version 2.2.0-beta.2835065501\ndotnet add package FluentAssertions --version 6.7.0\n````\n\nChange to the `Application` project directory and add additional libraries.\n\n* PostgreSQL Provider for EF Core\n* Codegenerators for REST Controllers\n\n````shell\n cd ..\\Application\n dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL --version 6.0.7\n dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design\n dotnet add package Microsoft.EntityFrameworkCore.Design\n dotnet add package Microsoft.EntityFrameworkCore.SqlServer\n````\n\n#### Additional Code\n\nFrom here on open the solution with an IDE. I am using Rider.\n\nAdapted from [this tutorial](https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-6.0\u0026tabs=visual-studio-code) we will create a ToDo Application.\n\nThe webapi template sets up a REST api which provides Information as defined in `WeatherForecast.cs`. We will add an additional REST api to create, read and delete ToDo items.\n\n#### Model\n\nCreate a new folder `Models` and in it create a class `TodoItem` with the following content.\n\n````csharp\npublic class TodoItem\n{\n    public long Id { get; set; }\n    public string? Name { get; set; }\n    public bool IsComplete { get; set; }\n}\n````\n\nAlso in the `Models` folder create a class `TodoContext` for EF core with the following content.\n\n````csharp\npublic class TodoContext : DbContext\n{\n    public TodoContext(DbContextOptions\u003cTodoContext\u003e options)\n        : base(options)\n    {\n    }\n\n    public DbSet\u003cTodoItem\u003e TodoItems { get; set; } = null!;\n````\n\n#### Register the database context\n\nWe now have the model to work with. The next step is to configure the database. In `Program.cs` add the following line:\n\n````csharp\nbuilder.Services.AddDbContext\u003cTodoContext\u003e(options =\u003e\n    options.UseNpgsql(builder.Configuration.GetConnectionString(\"TodoContext\")));\n````\n\nand in `appsettings.Development.json` add a connection string so it looks like this:\n\n````csharp\n{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"ConnectionStrings\": {\n    \"TodoContext\": \"Host=localhost;Database=postgres;Username=postgres;Password=postgres\"\n  }\n}\n````\n\n#### Generate controller and migration\n\nIn the next step we use a code generator utility to quickly create the code to handle CRUD operations via REST.\n\n````csharp\ndotnet tool install -g dotnet-aspnet-codegenerator\ndotnet-aspnet-codegenerator controller -name TodoItemsController -async -api -m TodoItem -dc TodoContext -outDir Controllers\ndotnet ef migrations add InitialCreate\n````\n\nThis will create `TodoItemsController.cs` in the `Models` folder and add an EF core migration to create the needed table structure.\n\n#### Try out the application\n\nIf we want to give the api a test run by hand with the swagger ui or Postman we have to create a database and initialize it. We can do that by starting a docker container and running the EF core migration.\n\n````csharp\ndocker run --name testcontainers-demo -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres\ndotnet ef database update\n````\n\nWhen we now start the application we should see the swagger ui and can create and get TodoItems.\n\n#### Create a simple xUnit test with Alba\n\nTo automatically test the functionality we will use xUnit tests.\nWe first have to make the `Application` project visible to the `Application.Test` project so `Alba` can find the .NET 6 [WebApplicationBuilder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.webapplicationbuilder?view=aspnetcore-6.0).\n\nOpen the `Application.csproj` file and add the following to it:\n\n````csharp\n\u003cItemGroup\u003e\n    \u003cInternalsVisibleTo Include=\"Application.Tests\" /\u003e\n\u003c/ItemGroup\u003e\n````\n\nNow go into the `Application.Tests` project and open `UnitTest1.cs`. Replace the code of the first unit test so it looks like this:\n\n````csharp\n[Fact]\npublic async void Test1()\n{\n    await using var host = await AlbaHost.For\u003cProgram\u003e(x =\u003e { x.ConfigureServices((context, services) =\u003e { }); });\n\n    var result = await host.GetAsJson\u003cList\u003cWeatherForecast\u003e\u003e(\"/WeatherForecast\");\n\n    result.Should().HaveCount(5);\n}\n````\n\nAs you can see we are using Alba to start the whole Application project in memory and communicate with it using some helper methods. The test shows that the normal WeatherForecasts API which does not use any database is working.\n\n#### Add testcontainers\n\nTo test the TodoApi we need to have a running database. We will use testcontainers to provide us with a fresh instance.\n\nLet the `UnitTest1.cs` class extend `IAsyncLifetime` and add the following code to it. This will create a postgres container, start it before any test methods run and remove it again if all tests are finished running or are aborted.\n\n````csharp\nprivate readonly TestcontainerDatabase Testcontainer = new TestcontainersBuilder\u003cPostgreSqlTestcontainer\u003e()\n    .WithDatabase(new PostgreSqlTestcontainerConfiguration\n    {\n        Database = \"postgres\",\n        Username = \"postgres\",\n        Password = \"Password12!\",\n    })\n    .Build();\n\npublic async Task InitializeAsync()\n{\n    await Testcontainer.StartAsync();\n}\n\npublic Task DisposeAsync()\n{\n    return Testcontainer.DisposeAsync().AsTask();\n}\n````\n\nNow add a new test to test the ToDoItem API. Here we are overwriting the ConnectionString for the database using the value of the testcontainer. The database will be completely fresh so we will have to run the migrations on it we created earlier. We will then use the same Alba helper methods as in Test1.\n\n````csharp\n[Fact]\n    public async void Test2()\n    {\n        await using var host = await AlbaHost.For\u003cProgram\u003e(builder =\u003e\n        {\n            builder.UseSetting(\"ConnectionStrings:TodoContext\", Testcontainer.ConnectionString);\n            builder.ConfigureServices((_, services) =\u003e\n                services.BuildServiceProvider().GetService\u003cTodoContext\u003e()?.Database.Migrate());\n        });\n\n        await host.Scenario(_ =\u003e\n        {\n            _.StatusCodeShouldBe(201);\n            _.Post\n                .Json(new TodoItem\n                {\n                    Id = 1L,\n                    Name = \"TestName\",\n                    IsComplete = false\n                })\n                .ToUrl(\"/api/TodoItems\");\n        });\n\n        var result = await host.GetAsJson\u003cTodoItem\u003e(\"/api/TodoItems/1\");\n\n        result?.Name.Should().Be(\"TestName\");\n    }\n````\n\nWe now have a running Todo API which we can automatically test end to end locally as well as in a CI pipeline without needing any external dependencies or mocks.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feifinger%2Fdotnet-testcontainers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feifinger%2Fdotnet-testcontainers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feifinger%2Fdotnet-testcontainers/lists"}