Skip to content

Commit 77c509a

Browse files
jhartmann123davidroth
authored andcommitted
improvement(#60): Document DbContextFactory, added TestContainers
1 parent 55330e2 commit 77c509a

34 files changed

+621
-53
lines changed

.gitlab-ci.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ stages:
1818

1919
variables:
2020
BUILD_IMAGE: mcr.microsoft.com/dotnet/sdk:10.0
21+
DOCKER_VERSION: "29"
22+
DOCKER_BUILDKIT: 1
23+
DOCKER_HOST: tcp://docker:2376
24+
DOCKER_TLS_CERTDIR: "/certs/${CI_JOB_ID}"
25+
DOCKER_TLS_VERIFY: 1
26+
DOCKER_CERT_PATH: "/certs/${CI_JOB_ID}/client"
27+
TESTCONTAINERS_RYUK_DISABLED: "true" # Disable Ryuk as we're running in dind-rootless and there is no docker.sock available.
2128

2229
default:
2330
interruptible: true
@@ -45,12 +52,7 @@ build:
4552

4653
dotnet:test:
4754
services:
48-
- name: postgres:17
49-
alias: postgres
50-
variables:
51-
POSTGRES_PASSWORD: admin
52-
ConnectionStrings__Npgsql: Host=postgres;Database=test_npgsql;Username=postgres;Password=admin
53-
ConnectionStrings__Hangfire: Host=postgres;Database=test_hangfire;Username=postgres;Password=admin
55+
- docker:${DOCKER_VERSION}-dind-rootless
5456

5557
release:lint-merge-request:
5658
stage: test

Extensions.slnx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<Project Path="src/Hosting/test/Hosting.Tests.csproj" />
1717
<Project Path="src/Mediator/test/Mediator.Tests.csproj" />
1818
<Project Path="src/UnitTests.EntityFrameworkCore.Npgsql/test/UnitTests.EntityFrameworkCore.Npgsql.Tests.csproj" />
19+
<Project Path="src/UnitTests.EntityFrameworkCore.SqlServer/test/UnitTests.EntityFrameworkCore.SqlServer.Tests.csproj" />
1920
<Project Path="src/UnitTests.EntityFrameworkCore/test/UnitTests.EntityFrameworkCore.Tests.csproj" />
2021
</Folder>
2122
<Project Path="src/AspNetCore/src/AspNetCore.csproj" />
@@ -25,6 +26,7 @@
2526
<Project Path="src/Hangfire/src/Hangfire.csproj" />
2627
<Project Path="src/Hosting/src/Hosting.csproj" />
2728
<Project Path="src/Mediator/src/Mediator.csproj" />
29+
<Project Path="src/TestContainers/src/TestContainers.csproj" />
2830
<Project Path="src/UnitTests.EntityFrameworkCore.Npgsql/src/UnitTests.EntityFrameworkCore.Npgsql.csproj" />
2931
<Project Path="src/UnitTests.EntityFrameworkCore.SqlServer/src/UnitTests.EntityFrameworkCore.SqlServer.csproj" />
3032
<Project Path="src/UnitTests.EntityFrameworkCore/src/UnitTests.EntityFrameworkCore.csproj" />

README.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,11 @@ Provides services and extensions for hosting. See the [documentation](docs/Hosti
3939
[![NuGet](https://img.shields.io/nuget/v/Fusonic.Extensions.Mediator.svg?label=Fusonic.Extensions.Mediator&style=plastic)](https://www.nuget.org/packages/Fusonic.Extensions.Mediator/)
4040
Provides simple mediator implementation. See the [documentation](docs/Mediator/README.md) for more details.
4141

42-
[![NuGet](https://img.shields.io/nuget/v/Fusonic.Extensions.UnitTests.svg?label=Fusonic.Extensions.UnitTests&style=plastic)](https://www.nuget.org/packages/Fusonic.Extensions.UnitTests/)
43-
Xunit-based testing base classes with support for dependency injection. Libraries supporting specific DI containers (SimpleInjector, ServiceProvider) are in separate packages. See the [unit test documentation](docs/UnitTests/README.md) for more details.
44-
45-
[![NuGet](https://img.shields.io/nuget/v/Fusonic.Extensions.UnitTests.ServiceProvider.svg?label=Fusonic.Extensions.UnitTests.ServiceProvider&style=plastic)](https://www.nuget.org/packages/Fusonic.Extensions.UnitTests.ServiceProvider/)
46-
Xunit-based testing base classes. Supports dependency injection with Microsofts Dependency Injection framework (ServiceProvider).. See the [unit test documentation](docs/UnitTests/README.md) for more details.
42+
[![NuGet](https://img.shields.io/nuget/v/Fusonic.Extensions.TestContainers.svg?label=Fusonic.Extensions.TestContainers&style=plastic)](https://www.nuget.org/packages/Fusonic.Extensions.TestContainers/)
43+
Provides simple mediator implementation. See the [documentation](docs/Mediator/README.md) for more details.
4744

48-
[![NuGet](https://img.shields.io/nuget/v/Fusonic.Extensions.UnitTests.SimpleInjector.svg?label=Fusonic.Extensions.UnitTests.SimpleInjector&style=plastic)](https://www.nuget.org/packages/Fusonic.Extensions.UnitTests.SimpleInjector/)
49-
Xunit-based testing base classes. Supports dependency injection with SimpleInjector.. See the [unit test documentation](docs/UnitTests/README.md) for more details.
45+
[![NuGet](https://img.shields.io/nuget/v/Fusonic.Extensions.UnitTests.svg?label=Fusonic.Extensions.UnitTests&style=plastic)](https://www.nuget.org/packages/Fusonic.Extensions.UnitTests/)
46+
Experimental package that provides a simple TestContainer setup for XUnit v3 test assemblies. See the [test containers documentation](docs/TestContainers/README.md) for more details.
5047

5148
[![NuGet](https://img.shields.io/nuget/v/Fusonic.Extensions.UnitTests.EntityFrameworkCore.svg?label=Fusonic.Extensions.UnitTests.EntityFrameworkCore&style=plastic)](https://www.nuget.org/packages/Fusonic.Extensions.UnitTests.EntityFrameworkCore/)
5249
Adds database support using EF Core to the unit tests. See the [unit test documentation](docs/UnitTests/README.md) for more details.

docs/TestContainers/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Test containers
2+
3+
EXPERIMENTAL. APIs might change any time as this library is currently only targeted for internal testing.
4+
5+
`Fusonic.Extensions.TestContainers` provides XUnit v3 startup classes that start some commonly used test containers.
6+
7+
When running the tests within a GitLab, GitHub or Azure DevOps-CI pipeline, the test containers get random names and get cleaned up after test execution.
8+
9+
Outside of a (known) CI pipeline, the test containers get stable names and do not get cleaned up after test execution. This is intended to speed up repeated test execution in local dev environments.
10+
11+
Example usage:
12+
```cs
13+
// In your test Project
14+
using MyProject.Tests;
15+
using Microsoft.Data.SqlClient;
16+
using Xunit.Sdk;
17+
using Xunit.v3;
18+
19+
[assembly: TestPipelineStartup(typeof(TestStartup))]
20+
21+
namespace MyProject.Tests;
22+
23+
public class TestStartup : TestContainerStartup
24+
{
25+
public static string ConnectionString { get; private set; } = null!;
26+
27+
public override async ValueTask StartAsync(IMessageSink diagnosticMessageSink)
28+
{
29+
var container = await StartMsSql();
30+
ConnectionString = new SqlConnectionStringBuilder(container.GetConnectionString())
31+
{
32+
InitialCatalog = "testtemplate"
33+
}.ConnectionString;
34+
}
35+
}
36+
```
37+
38+
## Running in GitLab
39+
40+
In order to use TestContainers in GitLab, start `docker:dind-rootless` as a service. When running rootless dind, you also must set `TESTCONTAINERS_RYUK_DISABLED` to `true`, as there is no `docker.sock` available. Ryuk is responsible for cleaning up the test containers, even when test jobs get cancelled. Disabling it should be safe though, as the containers started within `docker:dind` get cleaned up anyway.
41+
42+
Example:
43+
```yaml
44+
variables:
45+
DOCKER_VERSION: "29"
46+
DOCKER_BUILDKIT: 1
47+
DOCKER_HOST: tcp://docker:2376
48+
DOCKER_TLS_CERTDIR: "/certs/${CI_JOB_ID}"
49+
DOCKER_TLS_VERIFY: 1
50+
DOCKER_CERT_PATH: "/certs/${CI_JOB_ID}/client"
51+
TESTCONTAINERS_RYUK_DISABLED: "true" # Disable Ryuk as we're running in dind-rootless and there is no docker.sock available.
52+
53+
dotnet:test:
54+
services:
55+
- docker:${DOCKER_VERSION}-dind-rootless
56+
script:
57+
- echo "Running Tests..."
58+
```

docs/UnitTests/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ public class TestFixture : ServiceProviderTestFixture
167167

168168
The interface of `ITestStore` is straight forward. You can easily replace your test store with something else for another strategy or for supporting other databases.
169169

170+
When using `IDbContextFactory`, the factory must be registered with scoped lifetime, not with the default singleton lifetime.
171+
172+
```cs
173+
services.AddDbContext<AppDbContext>(b => b.UseNpgsqlDatabasePerTest(testStore), ServiceLifetime.Scoped);
174+
```
175+
170176
### PostgreSQL - Template
171177

172178
When using the `NpgsqlDatabasePerTest` it is assumed that you use a prepared database template. This template should have all migrations applied and may contain some seeded data. Each test gets a copy of this template. With the `PostgreSqlUtil`, we provide an easy way to create such a template.
@@ -254,6 +260,11 @@ The connection string must have the `Intial catalog` set. It determines the name
254260

255261
The `TemplateCreator` specifies the method to create a template. It has to create and seed the database and create a backup for the copies used for the tests. Fortunately, the `SqlServerTestUtil` provides a method to do exactly that.
256262

263+
When using `IDbContextFactory`, the factory must be registered with scoped lifetime, not with the default singleton lifetime.
264+
265+
```cs
266+
services.AddDbContext<AppDbContext>(b => b.UseSqlServerDatabasePerTest(testStore), ServiceLifetime.Scoped);
267+
```
257268

258269
### Configuring any other database
259270

src/Directory.Packages.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<PackageVersion Include="Hangfire.Core" Version="1.8.21" />
1212
<PackageVersion Include="Hangfire.PostgreSql" Version="1.20.12" />
1313
<PackageVersion Include="MailKit" Version="4.14.1" />
14+
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.3" />
1415
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0-rc.1.25451.107" />
1516
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.0-rc.1.25451.107" />
1617
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.0-rc.1.25451.107" />
@@ -32,6 +33,7 @@
3233
<PackageVersion Include="Microsoft.Testing.Extensions.TrxReport" Version="1.8.4" />
3334
<PackageVersion Include="netDumbster" Version="3.1.1" />
3435
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
36+
<PackageVersion Include="Npgsql" Version="9.0.4" />
3537
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0-rc.1" />
3638
<PackageVersion Include="NSubstitute" Version="5.3.0" />
3739
<PackageVersion Include="OpenTelemetry.Api" Version="1.13.1" />
@@ -43,6 +45,9 @@
4345
<PackageVersion Include="SimpleInjector.Integration.GenericHost" Version="5.5.0" />
4446
<PackageVersion Include="SimpleInjector" Version="5.5.0" />
4547
<PackageVersion Include="SimpleInjector.Integration.ServiceCollection" Version="5.5.0" />
48+
<PackageVersion Include="Testcontainers.MsSql" Version="4.8.1" />
49+
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.8.1" />
50+
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
4651
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
4752
<PackageVersion Include="xunit.v3.core" Version="3.1.0" />
4853
<PackageVersion Include="xunit.analyzers" Version="1.24.0" />

src/Email/test/SendEmailWithMockedClientTests.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
// Licensed under the MIT License. See LICENSE file in the project root for license information.
33

44
using System.Globalization;
5-
using AwesomeAssertions;
65
using Fusonic.Extensions.Email.Tests.Models;
76
using MimeKit;
87
using NSubstitute;
98
using NSubstitute.ClearExtensions;
109
using SimpleInjector;
11-
using Xunit;
1210

1311
namespace Fusonic.Extensions.Email.Tests;
1412

src/Hangfire/test/Hangfire.Tests.csproj

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,10 @@
3333
</ItemGroup>
3434

3535
<ItemGroup>
36+
<ProjectReference Include="..\..\TestContainers\src\TestContainers.csproj" />
3637
<ProjectReference Include="..\..\UnitTests.EntityFrameworkCore.Npgsql\src\UnitTests.EntityFrameworkCore.Npgsql.csproj" />
3738
<ProjectReference Include="..\..\UnitTests.EntityFrameworkCore\src\UnitTests.EntityFrameworkCore.csproj" />
3839
<ProjectReference Include="..\..\UnitTests\src\UnitTests.csproj" />
3940
<ProjectReference Include="..\src\Hangfire.csproj" />
4041
</ItemGroup>
41-
42-
<ItemGroup>
43-
<None Update="testsettings.json">
44-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
45-
</None>
46-
</ItemGroup>
4742
</Project>

src/Hangfire/test/TestFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private void RegisterDatabase(IServiceCollection services)
5151
var testStoreOptions = new NpgsqlDatabasePerTestStoreOptions
5252
{
5353
TemplateCreator = CreateDatabase,
54-
ConnectionString = Configuration.GetConnectionString("Hangfire")!
54+
ConnectionString = TestStartup.ConnectionString
5555
};
5656

5757
var testStore = new NpgsqlDatabasePerTestStore(testStoreOptions);

src/Hangfire/test/TestStartup.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Fusonic GmbH. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE file in the project root for license information.
3+
4+
using Fusonic.Extensions.Hangfire.Tests;
5+
using Npgsql;
6+
using Xunit.Sdk;
7+
using Xunit.v3;
8+
9+
[assembly: TestPipelineStartup(typeof(TestStartup))]
10+
11+
namespace Fusonic.Extensions.Hangfire.Tests;
12+
13+
public class TestStartup : TestContainerStartup
14+
{
15+
public static string ConnectionString { get; private set; } = null!;
16+
17+
public override async ValueTask StartAsync(IMessageSink diagnosticMessageSink)
18+
{
19+
var container = await StartPostgreSql();
20+
ConnectionString = new NpgsqlConnectionStringBuilder(container.GetConnectionString())
21+
{
22+
Database = "hangfire"
23+
}.ConnectionString;
24+
}
25+
}

0 commit comments

Comments
 (0)