using System; using System.Net.Http; using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Domain.Model; using BuildingBlocks.EFCore; using BuildingBlocks.MassTransit; using BuildingBlocks.Web; using Flight; using Flight.Data; using Flight.Data.Seed; using MassTransit; using MassTransit.Testing; using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NSubstitute; using Respawn; using Xunit; namespace Integration.Test; [CollectionDefinition(nameof(TestFixture))] public class TestFixtureCollection : ICollectionFixture { } // ref: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0 // ref: https://github.com/jbogard/ContosoUniversityDotNetCore-Pages/blob/master/ContosoUniversity.IntegrationTests/SliceFixture.cs // ref: https://github.com/jasontaylordev/CleanArchitecture/blob/main/tests/Application.IntegrationTests/Testing.cs public class TestFixture : IAsyncLifetime { private Checkpoint _checkpoint; private HttpClient _client; private IConfiguration _configuration; private WebApplicationFactory _factory; private ITestHarness _harness; private IServiceScopeFactory _scopeFactory; public ILogger Logger => _scopeFactory.CreateScope().ServiceProvider.GetRequiredService>(); public async Task InitializeAsync() { Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "test"); _factory = new WebApplicationFactory() .WithWebHostBuilder(builder => builder.ConfigureServices(services => { services.RemoveAll(typeof(IHostedService)); services.ReplaceSingleton(AddHttpContextAccessorMock); services.ReplaceScoped(); services.AddMassTransitTestHarness(x => { x.AddConsumer(); x.UsingRabbitMq((context, cfg) => { var rabbitMqOptions = services.GetOptions("RabbitMq"); var host = rabbitMqOptions.HostName; cfg.Host(host, h => { h.Username(rabbitMqOptions.UserName); h.Password(rabbitMqOptions.Password); }); cfg.ConfigureEndpoints(context); }); }); //FlightConsumer })); _harness = _factory.Services.GetTestHarness(); await _harness.Start(); _configuration = _factory.Services.GetRequiredService(); _scopeFactory = _factory.Services.GetRequiredService(); _client = _factory.CreateClient(new WebApplicationFactoryClientOptions {AllowAutoRedirect = false}); _checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}}; await EnsureDatabaseAsync(); } public async Task DisposeAsync() { _harness.Cancel(); await _factory.DisposeAsync(); await _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection")); } public async Task ExecuteScopeAsync(Func action) { using var scope = _scopeFactory.CreateScope(); await action(scope.ServiceProvider); } public async Task ExecuteScopeAsync(Func> action) { using var scope = _scopeFactory.CreateScope(); var result = await action(scope.ServiceProvider); return result; } public Task ExecuteDbContextAsync(Func action) { return ExecuteScopeAsync(sp => action(sp.GetService())); } public Task ExecuteDbContextAsync(Func action) { return ExecuteScopeAsync(sp => action(sp.GetService()).AsTask()); } public Task ExecuteDbContextAsync(Func action) { return ExecuteScopeAsync(sp => action(sp.GetService(), sp.GetService())); } public Task ExecuteDbContextAsync(Func> action) { return ExecuteScopeAsync(sp => action(sp.GetService())); } public Task ExecuteDbContextAsync(Func> action) { return ExecuteScopeAsync(sp => action(sp.GetService()).AsTask()); } public Task ExecuteDbContextAsync(Func> action) { return ExecuteScopeAsync(sp => action(sp.GetService(), sp.GetService())); } public Task InsertAsync(params T[] entities) where T : class { return ExecuteDbContextAsync(db => { foreach (var entity in entities) db.Set().Add(entity); return db.SaveChangesAsync(); }); } public Task InsertAsync(TEntity entity) where TEntity : class { return ExecuteDbContextAsync(db => { db.Set().Add(entity); return db.SaveChangesAsync(); }); } public Task InsertAsync(TEntity entity, TEntity2 entity2) where TEntity : class where TEntity2 : class { return ExecuteDbContextAsync(db => { db.Set().Add(entity); db.Set().Add(entity2); return db.SaveChangesAsync(); }); } public Task InsertAsync(TEntity entity, TEntity2 entity2, TEntity3 entity3) where TEntity : class where TEntity2 : class where TEntity3 : class { return ExecuteDbContextAsync(db => { db.Set().Add(entity); db.Set().Add(entity2); db.Set().Add(entity3); return db.SaveChangesAsync(); }); } public Task InsertAsync(TEntity entity, TEntity2 entity2, TEntity3 entity3, TEntity4 entity4) where TEntity : class where TEntity2 : class where TEntity3 : class where TEntity4 : class { return ExecuteDbContextAsync(db => { db.Set().Add(entity); db.Set().Add(entity2); db.Set().Add(entity3); db.Set().Add(entity4); return db.SaveChangesAsync(); }); } public Task FindAsync(long id) where T : class, IEntity { return ExecuteDbContextAsync(db => db.Set().FindAsync(id).AsTask()); } public Task SendAsync(IRequest request) { return ExecuteScopeAsync(sp => { var mediator = sp.GetRequiredService(); return mediator.Send(request); }); } public Task SendAsync(IRequest request) { return ExecuteScopeAsync(sp => { var mediator = sp.GetRequiredService(); return mediator.Send(request); }); } // ref: https://github.com/MassTransit/MassTransit/blob/00d6992286911a437b63b93c89a56e920b053c11/src/MassTransit.TestFramework/InMemoryTestFixture.cs // ref: https://wrapt.dev/blog/building-an-event-driven-dotnet-application-integration-testing /// /// Publishes a message to the bus, and waits for the specified response. /// /// The message that should be published. /// The message that should be published. public async Task PublishMessage(object message) where TMessage : class { await _harness.Bus.Publish(message); } /// /// Confirm if there was a fault when publishing for this harness. /// /// The message that should be published. /// A boolean of true if there was a fault for a message of the given type when published. public Task IsFaultyPublished() where TMessage : class { return _harness.Published.Any>(); } /// /// Confirm that a message has been published for this harness. /// /// The message that should be published. /// A boolean of true if a message of the given type has been published. public Task IsPublished() where TMessage : class { return _harness.Published.Any(); } /// /// Confirm that a message has been consumed for this harness. /// /// The message that should be consumed. /// A boolean of true if a message of the given type has been consumed. public Task IsConsumed() where TMessage : class { return _harness.Consumed.Any(); } /// /// The desired consumer consumed the message. /// /// The message that should be consumed. /// The consumer of the message. /// A boolean of true if a message of the given type has been consumed by the given consumer. public Task IsConsumed() where TMessage : class where TConsumedBy : class, IConsumer { using var scope = _scopeFactory.CreateScope(); var consumerHarness = scope.ServiceProvider.GetRequiredService>(); return consumerHarness.Consumed.Any(); } private async Task EnsureDatabaseAsync() { using var scope = _scopeFactory.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var seeders = scope.ServiceProvider.GetServices(); await context.Database.MigrateAsync(); foreach (var seeder in seeders) await seeder.SeedAllAsync(); } // private async Task AddInMemoryHarnessAsync() // { // _harness = _factory.Services.GetRequiredService(); // await _harness.Start(); // } private IHttpContextAccessor AddHttpContextAccessorMock(IServiceProvider serviceProvider) { var httpContextAccessorMock = Substitute.For(); using var scope = serviceProvider.CreateScope(); httpContextAccessorMock.HttpContext = new DefaultHttpContext {RequestServices = scope.ServiceProvider}; httpContextAccessorMock.HttpContext.Request.Host = new HostString("localhost", 5000); httpContextAccessorMock.HttpContext.Request.Scheme = "http"; return httpContextAccessorMock; } }