I have an integration tests project that executes as expected in VS. The integration tests use a MsSql testcontainer (from https://dotnet.testcontainers.org/).
My goal is to run these tests in an Azure DevOps pipeline within a docker image, as I do successfully for other projects which do not use testcontainers. For now I am just trying to run the tests within a docker image in my local machine. Unfortunately I am facing connection issues.
My environment:
- .NET 6
- OS: Windows
- Docker Desktop with linux containers
My code:
Authentication.Api/MyProject.Authentication.Api/Dockerfile:
##########################################################
# build
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["Authentication.Api/MyProject.Authentication.Api/MyProject.Authentication.Api.csproj", "Authentication.Api/MyProject.Authentication.Api/"]
COPY ["Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj", "Authentication.Api/MyProject.Authentication.Api.IntegrationTests/"]
RUN dotnet restore "Authentication.Api/MyProject.Authentication.Api/MyProject.Authentication.Api.csproj"
RUN dotnet restore "Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj"
COPY . .
WORKDIR "/src/Authentication.Api/MyProject.Authentication.Api"
RUN dotnet build "MyProject.Authentication.Api.csproj" -c Release -o /app/build
WORKDIR "/src/Authentication.Api/MyProject.Authentication.Api.IntegrationTests"
RUN dotnet build -c Release
##########################################################
# run test projects
FROM build AS tests
WORKDIR /src
VOLUME /var/run/docker.sock:/var/run/docker.sock
RUN dotnet test --no-build -c Release --results-directory /testresults --logger "trx;LogFileName=testresults_authentication_api_it.trx" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2cCobertura /p:CoverletOutput=/testresults/coverage/ -p:MergeWith=/testresults/coverage/coverage.json Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj
##########################################################
# create image
FROM build AS publish
WORKDIR "/src/Authentication.Api/MyProject.Authentication.Api"
RUN dotnet publish "MyProject.Authentication.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS final
WORKDIR /app
EXPOSE 80
EXPOSE 443
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyProject.Authentication.Api.dll"]
Authentication.Api/MyProject.Authentication.Api.IntegrationTests/Factory/CustomWebApplicationFactory.cs:
public class CustomWebApplicationFactory<TProgram, TDbContext> : WebApplicationFactory<TProgram>, IAsyncLifetime, ICustomWebApplicationFactory
where TProgram : class
where TDbContext : DbContext
{
private readonly MsSqlDatabaseProvider _applicationMsSqlDatabaseProvider;
public CustomWebApplicationFactory()
{
_applicationMsSqlDatabaseProvider = new MsSqlDatabaseProvider();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
=> builder.ConfigureServices(services =>
{
services.Remove(services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>)) ?? throw new InvalidOperationException());
services.AddDbContext<ApplicationDbContext>(options => { options.UseSqlServer(_applicationMsSqlDatabaseProvider.Database.ConnectionString); });
ServiceProvider? sp = services.BuildServiceProvider();
using IServiceScope scope = sp.CreateScope();
IServiceProvider scopedServices = scope.ServiceProvider;
ILogger<CustomWebApplicationFactory<TProgram, TDbContext>> logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TProgram, TDbContext>>>();
ApplicationDbContext applicationDbContext = scopedServices.GetRequiredService<ApplicationDbContext>();
applicationDbContext.Database.EnsureCreated();
logger.LogInformation("Ensured that the ApplicationDbContext DB is created.");
});
public async Task InitializeAsync() =>
await _applicationMsSqlDatabaseProvider.Database.StartAsync();
public new async Task DisposeAsync() =>
await _applicationMsSqlDatabaseProvider.Database.DisposeAsync().AsTask();
}
{shared library path}/MsSqlDatabaseProvider.cs:
public class MsSqlDatabaseProvider
{
private const string DbPassword = "my_dummy_password#123";
private const string DbImage = "mcr.microsoft.com/mssql/server:2019-latest";
public readonly TestcontainerDatabase Database;
public MsSqlDatabaseProvider() =>
Database = new TestcontainersBuilder<MsSqlTestcontainer>()
.WithDatabase(new MsSqlTestcontainerConfiguration
{
Password = DbPassword,
})
.WithImage(DbImage)
.WithCleanUp(true)
.Build();
}
On command line I run docker build --progress=plain -f Authentication.ApiMyProject.Authentication.ApiDockerfile --target tests --tag myproject-tests .
.
And I am getting the following error:
Cannot detect the Docker endpoint. Use either the environment variables or the ~/.testcontainers.properties file to customize your configuration: https://dotnet.testcontainers.org/custom_configuration/ (Parameter ‘DockerEndpointAuthConfig’)
I tried adding the environment variable in docker, changing dockerfile to
RUN export DOCKER_HOST="tcp://192.168.99.100:2376" && dotnet test --no-build -c Release --results-directory /testresults --logger "trx;LogFileName=testresults_authentication_api_it.trx" /p:CollectCoverage=true /p:CoverletOutputFormat=json%2cCobertura /p:CoverletOutput=/testresults/coverage/ -p:MergeWith=/testresults/coverage/coverage.json Authentication.Api/MyProject.Authentication.Api.IntegrationTests/MyProject.Authentication.Api.IntegrationTests.csproj
and adding .WithDockerEndpoint("tcp://192.168.99.100:2376")
in MsSqlDatabaseProvider, but I ended up with another error:
System.Net.Http.HttpRequestException : Connection failed
System.Net.Sockets.SocketException : Connection refused
I do not know what value(s) I should use for docker host / docker endpoint. Or is the solution something else?
Thank you in advance!
2
Answers
I could manage to do it, with two major differences:
docker compose
now.docker-compose-tests.yml:
("sh" command is useful if more test projects are expected to run.)
Authentication.Api/MyProject.Authentication.Api/Dockerfile:
Authentication.Api/MyProject.Authentication.Api.IntegrationTests/Factory/CustomWebApplicationFactory.cs: same as in the question.
{shared library path}/MsSqlDatabaseProvider.cs:
And I can run the tests in docker with
docker compose -f docker-compose-tests.yml up myproject.authentication.api.tests
.Disclaimer:
I am a maintainer of Testcontainers for .NET and work as an engineer at AtomicJar, the company behind Testcontainers and Testcontainers Cloud.
Running Testcontainers in a Docker image build is very difficult. It is challenging (but possible) to provide access to a Docker endpoint running outside and independent of the
docker build
process. Usually, it requires a complex setup. As a much simpler solution, leveraging Testcontainers Cloud as part of the Docker image build works surprisingly well. The following Dockerfile configuration runs the Testcontainers Cloud agent inside the Docker image build (L:5):Running
docker build --build-arg TC_CLOUD_TOKEN=${TC_CLOUD_TOKEN} .
spins up the test dependencies in Testcontainers Cloud. I ran a simple test against amssql/server:2022-latest
container:Make sure you are using a multi-stage build and not expose your token in a layer.