{"id":17947595,"url":"https://github.com/iang/dotnet-database-testcontainers-example","last_synced_at":"2025-10-06T17:55:51.119Z","repository":{"id":258260186,"uuid":"873118395","full_name":"IanG/dotnet-database-testcontainers-example","owner":"IanG","description":"A example of how to use a database testcontainer (postgresql) for integration testing","archived":false,"fork":false,"pushed_at":"2024-11-24T13:15:02.000Z","size":26,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-09T23:31:32.278Z","etag":null,"topics":["csharp","dotnet","dotnet-webapi","testcontainers","xunit"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IanG.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2024-10-15T16:21:52.000Z","updated_at":"2024-11-24T13:15:05.000Z","dependencies_parsed_at":"2025-08-09T23:25:18.409Z","dependency_job_id":"2ae247aa-7853-4407-92ce-126755aea14c","html_url":"https://github.com/IanG/dotnet-database-testcontainers-example","commit_stats":null,"previous_names":["iang/dotnet-database-testcontainers-example"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/IanG/dotnet-database-testcontainers-example","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IanG%2Fdotnet-database-testcontainers-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IanG%2Fdotnet-database-testcontainers-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IanG%2Fdotnet-database-testcontainers-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IanG%2Fdotnet-database-testcontainers-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IanG","download_url":"https://codeload.github.com/IanG/dotnet-database-testcontainers-example/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IanG%2Fdotnet-database-testcontainers-example/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278655138,"owners_count":26022967,"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-10-06T02:00:05.630Z","response_time":65,"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":["csharp","dotnet","dotnet-webapi","testcontainers","xunit"],"created_at":"2024-10-29T08:05:56.503Z","updated_at":"2025-10-06T17:55:51.062Z","avatar_url":"https://github.com/IanG.png","language":"C#","readme":"# dotnet-database-testcontainers-example\n\n## Introduction\n\nWhen creating integration tests it can often be that external resources such as databases are:\n\n- Based upon a [SQLite](https://www.sqlite.org/) database (via `Microsoft.EntityFrameworkCore.Sqlite`)\n- Based upon an in-memory database (via `Microsoft.EntityFrameworkCore.InMemory`) \n\nIn both of these cases you are not testing against the actual database you would be using in your production environment.  If you run against [PostgreSQL](https://www.postgresql.org/) in production, you should really be running your integration tests against the same database platform.\n\nThe challenge of this as close-to-production style of integration testing is having a PostgreSQL database consistently available without having to perform a lot of manual intervention.  This is before we even begin to consider how that database would be seeded with a schema and data to support whatever tests we intend to run.\n\n### Introducing Test Containers\n\nThe [Test Containers](https://testcontainers.com/) open source project solves this problem by providing throwaway instances of databases, message brokers and a whole host of other services via [Docker](https://www.docker.com/).  Test Containers allow us to:\n\n- Mirror as close to production infrastructure in our integration tests \n- Programatically spin up and configure containers before our integration tests commence\n- Automatically destroy containers upon completion of integration tests\n\nYou can view all the supported module/container types [here](https://testcontainers.com/modules/) and you will find great supporting documentation for use with the .NET Framework [here](https://dotnet.testcontainers.org/).\n\nFor the purposes of this example we want to use a PostgreSQL container loaded with movie data to support our `/api/movies` endpoint in our `API` project.\n\n**Note**: to use this project you will need to ensure you have Docker installed and running on your workstation.\n\n## Running/Debugging The Application\n\n### Setting up a PostgreSQL instance for development/debugging\n\nThere is `docker-compose.yml` file in the root of the cloned repository.  If you do:\n\n```\ndocker compose up -d\n```\nThis will start the database.   If you do:\n\n```\ndocker compose down --volumes\n```\nThis will stop the PostgreSQL container and remove its associated volumes.  If you wish to keep re-using the container once it is created just omit the `--volumes` part of the above command and your data will remain between up/down operations.\n\n#### How is the database schema and data created ?\n\nIn the root of the cloned repository you will find the `etc/docker-entrypoint-initdb.d` directory.  This directory is mounted into the container when it is created.  This directory contains a file called `01-create-movies-db.sql` which will be executed within the container the first time it starts.  This script creates:\n\n- A new database called `movies`\n- A new user called `moviesuser` with a password and appropriate permissions\n- Connects to the `movies` database and creates the tables and data required by the application\n\nThe `appsettings.json` file in the `API` project has a connection string called `MoviesDb` that can connect to this container whilst running and debugging.\n\n## The `test/Integration` Project.\n\nIn this project we depend upon the following Test Containers nuget package:\n\n- `Testcontainers.PostgreSql`\n\nThis nuget package provides the ability to create short-lived PostgreSQL docker image configured with:\n- A specific PostreSQL image/version\n- A named database with a username and password\n- Port Bindings\n- Volume Bindings\n\nThe tests in this project make use of [xUnit](https://xunit.net/) and [Fluent Assertions](https://fluentassertions.com/) to orchestrate our tests.  \n\n### MoviesControllerTests \n\nIf we look at the test \n\n- `TestingContainersExample.Tests.Integration.API.Controllers.MoviesControllerTests` \n \nwe want to have our database available for the lifetime of the test suite.  We can use a `WebApplicationFactory` alongside a `PostgreSqlContainer` test to achieve this.\n\n### How does the test PostgreSQL container get created ?\n\nThe test class implements/extends `IClassFixture\u003cIntegrationTestWebApplicationFactory\u003e`.  xUnit [class fixtures](https://xunit.net/docs/shared-context) are a shared context that exists for all tests in the class. In our case this is:\n\n- A `WebApplicationFactory` hosting our API.\n- An instance of a `PostgreSqlContainer` we can connect to from our `MoviesDbContext` in the service collection of the web application.\n\nIn the constructor of `IntegrationTestWebApplicationFactory` we use a `PostgreSqlBuilder` to define what our PostgreSQL image should contain.  You will see it:\n\n- Uses the very latest PostgreSQL image `postgres:latest`\n- Names the database `movies`\n- Creates a user called `moviesuser` with an associated password\n- Mounts the `scripts/docker-entrypint-initdb.d` directory of the project into `/docker-entrypoint-initdb.d` within the container.  This directory contains the script `01-create-movies-db-data.sql` which will create our schema objects and data when the container starts.\n- Assigns a random external port from the container which can be used to connect to the database\n\nThis class also implements `IAsyncLifetime` and when the instance is given to the test class the `InitializeAsync()` method is called.   This will trigger the PostgreSQL Test Containers instance to start.  When the test class finishes with the fixture the `DisposeAsync()` method will be called.  This stops the PostgreSQL test container and destroys/removes it from your docker instance.\n\n#### How Does The Web Application Wire To The Database In The Test Container ?\n\nThis same class extends `WebApplicationFactory\u003cProgram\u003e`.  When the overriden `ConfigureWebHost` method is invoked it:\n\n- Finds and removes the `DbContextOptions\u003cMoviesDbContext\u003e` which was already loaded into the service collection with `services.AddDbContext\u003cMoviesDbContext\u003e` in `program.cs`\n- Calls `services.AddDbContext\u003cMoviesDbContext\u003e` to re-create the `MoviesDbContext` using the connection string obtained from the test container.\n\nAs we define the container with `.WithPortBinding(5432, assignRandomHostPort: true)` this means this PostgresSQL instance will not collide with any other instances of Postgres you may have running in Docker (especially if they use the default port `5432`)\n\n### MoviesServiceTests\n\nThis test provides a slightly different approach to using the PostgreSQL test container.  Within this test we are only testing the `TestingContainersExample.Common.Services.MovieService` class so we don't need the whole application to be spun up - we only require a `MoviesDbContext` in order to exercise the service.\n\nIf you look at `TestingContainersExample.Tests.Integration.Fixtures.MoviesDbContextFixture` you will see that this class only provides a mechanism to obtain a `MoviesDbContext`. The test goes on to create an instance of the `MoviesService` to which it can provide the `MoviesDbContext` created by the fixture class.\n\n## Conclusion\n\nUsing test containers provides a very nice mechanism to allow you to test your code in a way that more closely matches your production infrastructure.  In this instance we are only making use of a database test container but if you require other services like Kafka, RabbitMq, Redis etc. they are all supported.   Thanks for looking. ","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiang%2Fdotnet-database-testcontainers-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiang%2Fdotnet-database-testcontainers-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiang%2Fdotnet-database-testcontainers-example/lists"}