Skip to content

Commit 6b625a7

Browse files
committed
Improve IntegrationTest architecture
1 parent f6eee63 commit 6b625a7

File tree

7 files changed

+158
-48
lines changed

7 files changed

+158
-48
lines changed

API.IntegrationTests/BaseIntegrationTest.cs

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using DotNet.Testcontainers.Builders;
2+
using DotNet.Testcontainers.Networks;
3+
using Testcontainers.Redis;
4+
using TUnit.Core.Interfaces;
5+
6+
namespace OpenShock.API.IntegrationTests.Docker;
7+
8+
public sealed class DockerNetwork : IAsyncInitializer, IAsyncDisposable
9+
{
10+
public INetwork Instance { get; } = new NetworkBuilder()
11+
.WithName($"tunit-{Guid.CreateVersion7():N}")
12+
.Build();
13+
14+
public Task InitializeAsync() => Instance.CreateAsync();
15+
public ValueTask DisposeAsync() => Instance.DisposeAsync();
16+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Testcontainers.PostgreSql;
2+
using TUnit.Core.Interfaces;
3+
4+
namespace OpenShock.API.IntegrationTests.Docker;
5+
6+
public sealed class InMemoryDatabase : IAsyncInitializer, IAsyncDisposable
7+
{
8+
[ClassDataSource<DockerNetwork>(Shared = SharedType.PerTestSession)]
9+
public required DockerNetwork DockerNetwork { get; init; }
10+
11+
private PostgreSqlContainer? _container;
12+
public PostgreSqlContainer Container
13+
{
14+
get
15+
{
16+
_container ??= new PostgreSqlBuilder()
17+
.WithNetwork(DockerNetwork.Instance)
18+
.WithName($"tunit-postgresql-{Guid.CreateVersion7()}")
19+
.WithImage("postgres:latest")
20+
.WithPortBinding(5432, 5432)
21+
.WithDatabase("openshock")
22+
.WithUsername("openshock")
23+
.WithPassword("superSecurePassword")
24+
.Build();
25+
26+
return _container;
27+
}
28+
}
29+
30+
public Task InitializeAsync() => Container.StartAsync();
31+
public ValueTask DisposeAsync() => Container.DisposeAsync();
32+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Testcontainers.Redis;
2+
using TUnit.Core.Interfaces;
3+
4+
namespace OpenShock.API.IntegrationTests.Docker;
5+
6+
public sealed class InMemoryRedis : IAsyncInitializer, IAsyncDisposable
7+
{
8+
[ClassDataSource<DockerNetwork>(Shared = SharedType.PerTestSession)]
9+
public required DockerNetwork DockerNetwork { get; init; }
10+
11+
private RedisContainer? _container;
12+
public RedisContainer Container
13+
{
14+
get
15+
{
16+
_container ??= new RedisBuilder()
17+
.WithNetwork(DockerNetwork.Instance)
18+
.WithName($"tunit-redis-{Guid.CreateVersion7()}")
19+
.WithImage("redis/redis-stack-server:latest")
20+
.WithPortBinding(6379, 6379)
21+
.Build();
22+
23+
return _container;
24+
}
25+
}
26+
27+
public Task InitializeAsync() => Container.StartAsync();
28+
public ValueTask DisposeAsync() => Container.DisposeAsync();
29+
}

API.IntegrationTests/AccountTests.cs renamed to API.IntegrationTests/Tests/AccountTests.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
using Microsoft.Extensions.DependencyInjection;
66
using OpenShock.Common.OpenShockDb;
77

8-
namespace OpenShock.API.IntegrationTests;
8+
namespace OpenShock.API.IntegrationTests.Tests;
99

10-
public class AccountTests : BaseIntegrationTest
10+
public class AccountTests
1111
{
12+
[ClassDataSource<WebApplicationFactory>(Shared = SharedType.PerTestSession)]
13+
public required WebApplicationFactory WebApplicationFactory { get; init; }
14+
1215
[Test]
1316
public async Task CreateAccount_ShouldAdd_NewUserToDatabase()
1417
{
15-
using var client = WebAppFactory.CreateClient();
18+
using var client = WebApplicationFactory.CreateClient();
1619

1720
var requestBody = JsonSerializer.Serialize(new
1821
{
@@ -29,7 +32,7 @@ public async Task CreateAccount_ShouldAdd_NewUserToDatabase()
2932

3033
await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK);
3134

32-
await using var scope = WebAppFactory.Services.CreateAsyncScope();
35+
await using var scope = WebApplicationFactory.Services.CreateAsyncScope();
3336
var db = scope.ServiceProvider.GetRequiredService<OpenShockContext>();
3437

3538
var user = await db.Users.FirstOrDefaultAsync(u => u.Email == "bob@example.com");
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Net;
2+
using System.Net.Http.Json;
3+
4+
namespace OpenShock.API.IntegrationTests.Tests;
5+
6+
file sealed class Parent
7+
{
8+
public required string Message { get; init; }
9+
public required BackendInfoResponse Data { get; init; }
10+
}
11+
file sealed class BackendInfoResponse
12+
{
13+
public required string Version { get; init; }
14+
public required string Commit { get; init; }
15+
public required DateTimeOffset CurrentTime { get; init; }
16+
public required Uri FrontendUrl { get; init; }
17+
public required Uri ShortLinkUrl { get; init; }
18+
public required string? TurnstileSiteKey { get; init; }
19+
public required string[] OAuthProviders { get; init; }
20+
public required bool IsUserAuthenticated { get; init; }
21+
}
22+
23+
public class MetadataTests
24+
{
25+
[ClassDataSource<WebApplicationFactory>(Shared = SharedType.PerTestSession)]
26+
public required WebApplicationFactory WebApplicationFactory { get; init; }
27+
28+
[Test]
29+
public async Task GetMatadata_ShouldReturnOk()
30+
{
31+
using var client = WebApplicationFactory.CreateClient();
32+
33+
var response = await client.GetAsync("/1");
34+
35+
await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK);
36+
37+
var parent = await response.Content.ReadFromJsonAsync<Parent>();
38+
await Assert.That(parent).IsNotNull();
39+
await Assert.That(parent!.Message).IsEqualTo("OpenShock");
40+
41+
var content = parent.Data;
42+
await Assert.That(content).IsNotNull();
43+
44+
await Assert.That(content!.Version).IsNotNullOrWhitespace();
45+
await Assert.That(content!.Commit).IsNotNullOrWhitespace();
46+
await Assert.That(content!.CurrentTime).IsBetween(DateTimeOffset.UtcNow.AddSeconds(-5), DateTimeOffset.UtcNow.AddSeconds(5)); // Idk if this is ok way to do it
47+
await Assert.That(content!.TurnstileSiteKey).IsEqualTo("turnstile-site-key");
48+
}
49+
}
Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,51 @@
11
using Microsoft.AspNetCore.Hosting;
22
using Microsoft.AspNetCore.Mvc.Testing;
33
using Microsoft.AspNetCore.TestHost;
4+
using Microsoft.Extensions.Configuration;
45
using Microsoft.Extensions.DependencyInjection;
56
using Microsoft.Extensions.Http;
7+
using OpenShock.API.IntegrationTests.Docker;
68
using OpenShock.API.IntegrationTests.HttpMessageHandlers;
7-
using Testcontainers.PostgreSql;
8-
using Testcontainers.Redis;
99
using TUnit.Core.Interfaces;
1010

1111
namespace OpenShock.API.IntegrationTests;
1212

13-
public class IntegrationTestWebAppFactory : WebApplicationFactory<Program>, IAsyncInitializer
13+
public class WebApplicationFactory : WebApplicationFactory<Program>, IAsyncInitializer
1414
{
15-
private readonly PostgreSqlContainer _dbContainer = new PostgreSqlBuilder()
16-
.WithImage("postgres:latest")
17-
.WithDatabase("openshock")
18-
.WithUsername("openshock")
19-
.WithPassword("superSecurePassword")
20-
.Build();
21-
22-
private readonly RedisContainer _redisContainer = new RedisBuilder()
23-
.WithImage("redis/redis-stack-server:latest")
24-
.Build();
25-
15+
[ClassDataSource<InMemoryDatabase>(Shared = SharedType.PerTestSession)]
16+
public required InMemoryDatabase PostgreSql { get; init; }
17+
18+
[ClassDataSource<InMemoryRedis>(Shared = SharedType.PerTestSession)]
19+
public required InMemoryRedis Redis { get; init; }
2620

27-
public async Task InitializeAsync()
21+
public Task InitializeAsync()
2822
{
29-
await _dbContainer.StartAsync();
30-
await _redisContainer.StartAsync();
23+
_ = Server;
24+
return Task.CompletedTask;
3125
}
3226

33-
protected override IWebHostBuilder? CreateWebHostBuilder()
27+
protected override void ConfigureWebHost(IWebHostBuilder builder)
3428
{
29+
// TODO: Find a way to do the following instead of the current implementation
30+
/*
31+
builder.ConfigureAppConfiguration((_, configBuilder) =>
32+
{
33+
configBuilder.Sources.Clear();
34+
configBuilder.AddInMemoryCollection(new Dictionary<string, string?>
35+
{
36+
...
37+
});
38+
});
39+
*/
3540
var environmentVariables = new Dictionary<string, string>
3641
{
3742
{ "ASPNETCORE_UNDER_INTEGRATION_TEST", "1" },
3843

39-
{ "OPENSHOCK__DB__CONN", _dbContainer.GetConnectionString() },
44+
{ "OPENSHOCK__DB__CONN", PostgreSql.Container.GetConnectionString() },
4045
{ "OPENSHOCK__DB__SKIPMIGRATION", "false" },
4146
{ "OPENSHOCK__DB__DEBUG", "false" },
4247

43-
{ "OPENSHOCK__REDIS__CONN", _redisContainer.GetConnectionString() },
44-
{ "OPENSHOCK__REDIS__HOST", "" },
45-
{ "OPENSHOCK__REDIS__USER", "" },
46-
{ "OPENSHOCK__REDIS__PASSWORD", "" },
47-
{ "OPENSHOCK__REDIS__PORT", "6379" },
48+
{ "OPENSHOCK__REDIS__CONN", Redis.Container.GetConnectionString() },
4849

4950
{ "OPENSHOCK__FRONTEND__BASEURL", "https://openshock.app" },
5051
{ "OPENSHOCK__FRONTEND__SHORTURL", "https://openshock.app" },
@@ -73,22 +74,9 @@ public async Task InitializeAsync()
7374
Environment.SetEnvironmentVariable(envVar.Key, envVar.Value);
7475
}
7576

76-
return base.CreateWebHostBuilder();
77-
}
78-
79-
protected override void ConfigureWebHost(IWebHostBuilder builder)
80-
{
81-
8277
builder.ConfigureTestServices(services =>
8378
{
8479
services.AddTransient<HttpMessageHandlerBuilder, InterceptedHttpMessageHandlerBuilder>();
8580
});
8681
}
87-
88-
public override async ValueTask DisposeAsync()
89-
{
90-
await _dbContainer.DisposeAsync();
91-
await _redisContainer.DisposeAsync();
92-
await base.DisposeAsync();
93-
}
9482
}

0 commit comments

Comments
 (0)