This repository has been archived by the owner on Jul 12, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8e1c797
commit b1550b8
Showing
5 changed files
with
187 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
tests/RookieShop.IntegrationTests/Extensions/ApplicationExtension.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
using RookieShop.IntegrationTests.Fixtures; | ||
using RookieShop.Persistence; | ||
|
||
namespace RookieShop.IntegrationTests.Extensions; | ||
|
||
public static class ApplicationExtension | ||
{ | ||
public static async Task EnsureCreatedAsync<T>( | ||
this ApplicationFactory<T> factory, | ||
CancellationToken cancellationToken = default) | ||
where T : class | ||
{ | ||
await using var scope = factory.Instance.Services.CreateAsyncScope(); | ||
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); | ||
await dbContext.Database.EnsureCreatedAsync(cancellationToken); | ||
} | ||
|
||
public static async Task EnsureCreatedAndPopulateDataAsync<TProgram, TEntity>( | ||
this ApplicationFactory<TProgram> factory, | ||
IReadOnlyCollection<TEntity> entities, | ||
CancellationToken cancellationToken = default) | ||
where TProgram : class | ||
where TEntity : class | ||
{ | ||
await using var scope = factory.Instance.Services.CreateAsyncScope(); | ||
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); | ||
await dbContext.Database.EnsureCreatedAsync(cancellationToken); | ||
await dbContext.Set<TEntity>().AddRangeAsync(entities, cancellationToken); | ||
await dbContext.SaveChangesAsync(cancellationToken); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
tests/RookieShop.IntegrationTests/Extensions/TestContainersExtension.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using DotNet.Testcontainers.Containers; | ||
using Polly; | ||
|
||
namespace RookieShop.IntegrationTests.Extensions; | ||
|
||
public static class TestContainersExtension | ||
{ | ||
public static Task StartWithWaitAndRetryAsync( | ||
this IContainer container, | ||
int retryCount = 3, | ||
int retryDelay = 3, | ||
CancellationToken cancellationToken = default) => | ||
Policy | ||
.Handle<AggregateException>() | ||
.Or<InvalidOperationException>() | ||
.WaitAndRetryAsync(retryCount, _ => TimeSpan.FromSeconds(retryCount * retryDelay)) | ||
.ExecuteAsync(container.StartAsync, cancellationToken); | ||
} |
117 changes: 117 additions & 0 deletions
117
tests/RookieShop.IntegrationTests/Fixtures/ApplicationFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
using System.Diagnostics; | ||
using DotNet.Testcontainers.Containers; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Mvc.Testing; | ||
using RookieShop.IntegrationTests.Extensions; | ||
using Testcontainers.Azurite; | ||
using Testcontainers.PostgreSql; | ||
using Testcontainers.Redis; | ||
|
||
namespace RookieShop.IntegrationTests.Fixtures; | ||
|
||
public sealed class ApplicationFactory<TProgram> | ||
: WebApplicationFactory<TProgram>, IAsyncLifetime, IContextFixture where TProgram : class | ||
{ | ||
private readonly List<IContainer> _containers = []; | ||
private AzuriteContainer _storageContainer = default!; | ||
public WebApplicationFactory<TProgram> Instance { get; private set; } = default!; | ||
|
||
public Task InitializeAsync() | ||
{ | ||
Debug.WriteLine($"{nameof(ApplicationFactory<TProgram>)} called {nameof(InitializeAsync)}"); | ||
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Test"; | ||
Instance = WithWebHostBuilder(builder => builder.UseEnvironment(env)); | ||
return Task.CompletedTask; | ||
} | ||
|
||
public new Task DisposeAsync() | ||
{ | ||
Debug.WriteLine($"{nameof(ApplicationFactory<TProgram>)} called {nameof(DisposeAsync)}"); | ||
return Task | ||
.WhenAll(_containers.Select(container => container.DisposeAsync().AsTask())) | ||
.ContinueWith(async _ => await base.DisposeAsync()); | ||
} | ||
|
||
public ApplicationFactory<TProgram> WithCacheContainer() | ||
{ | ||
Debug.WriteLine($"{nameof(ApplicationFactory<TProgram>)} called {nameof(WithCacheContainer)}"); | ||
_containers.Add(new RedisBuilder() | ||
.WithName($"test_cache_{Guid.NewGuid()}") | ||
.WithImage("redis:alpine") | ||
.WithCleanUp(true) | ||
.Build()); | ||
|
||
return this; | ||
} | ||
|
||
public ApplicationFactory<TProgram> WithDbContainer() | ||
{ | ||
Debug.WriteLine($"{nameof(ApplicationFactory<TProgram>)} called {nameof(WithDbContainer)}"); | ||
_containers.Add(new PostgreSqlBuilder() | ||
.WithDatabase($"test_db_{Guid.NewGuid()}") | ||
.WithUsername("postgres") | ||
.WithPassword("postgres") | ||
.WithImage("postgres:alpine") | ||
.WithCleanUp(true) | ||
.Build()); | ||
|
||
return this; | ||
} | ||
|
||
public ApplicationFactory<TProgram> WithStorageContainer() | ||
{ | ||
Debug.WriteLine($"{nameof(ApplicationFactory<TProgram>)} called {nameof(WithStorageContainer)}"); | ||
_storageContainer = new AzuriteBuilder() | ||
.WithPortBinding(10000, true) | ||
.Build(); | ||
|
||
return this; | ||
} | ||
|
||
public async Task StartContainersAsync(CancellationToken cancellationToken = default) | ||
{ | ||
Debug.WriteLine($"{nameof(ApplicationFactory<TProgram>)} called {nameof(StartContainersAsync)}"); | ||
|
||
await _storageContainer.StartWithWaitAndRetryAsync(cancellationToken: cancellationToken); | ||
|
||
if (_containers.Count == 0) return; | ||
|
||
await Task.WhenAll(_containers.Select(container => | ||
container.StartWithWaitAndRetryAsync(cancellationToken: cancellationToken))); | ||
|
||
Instance = _containers.Aggregate(this as WebApplicationFactory<TProgram>, (current, container) => | ||
current.WithWebHostBuilder(builder => | ||
{ | ||
switch (container) | ||
{ | ||
case PostgreSqlContainer dbContainer: | ||
builder.UseSetting("ConnectionStrings:Postgres", dbContainer.GetConnectionString()); | ||
break; | ||
case RedisContainer cacheContainer: | ||
builder.UseSetting("Redis:Url", cacheContainer.GetConnectionString()); | ||
break; | ||
case AzuriteContainer storageContainer: | ||
builder.UseSetting("ConnectionStrings:Azurite", storageContainer.GetConnectionString()); | ||
break; | ||
} | ||
})); | ||
} | ||
|
||
public new HttpClient CreateClient() => Instance.CreateClient(); | ||
|
||
public async Task StopContainersAsync() | ||
{ | ||
Debug.WriteLine($"{nameof(ApplicationFactory<TProgram>)} called {nameof(StopContainersAsync)}"); | ||
|
||
if (_containers.Count == 0) return; | ||
|
||
await Task.WhenAll(_containers.Select(container => container.DisposeAsync().AsTask())) | ||
.ContinueWith(async _ => await base.DisposeAsync()) | ||
.ContinueWith(async _ => await InitializeAsync()) | ||
.ConfigureAwait(false); | ||
|
||
_containers.Clear(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters