From b2a81677081241fa8c8487135beb2d4b7f023745 Mon Sep 17 00:00:00 2001 From: "oskar.dudycz" Date: Thu, 13 May 2021 17:05:09 +0200 Subject: [PATCH] Refactored Core.Testing to allow tests without Startup class --- Core.Testing/ApiFixture.cs | 36 +++++++--- Core.Testing/TestContext.cs | 72 ++++++++++++------- Core.Testing/TestWebHostBuilder.cs | 33 +++++++++ .../EventSourcing.Sample.Web/Startup.cs | 3 +- .../Carts.Api.Tests/Carts/InitCartTests.cs | 4 +- .../Orders.Api.Tests/Carts/InitCartTests.cs | 2 +- .../Payments/RequestPaymentsTests.cs | 2 +- .../Packages/SendPackageTests.cs | 4 +- .../Meetings/CreateMeetingTests.cs | 4 +- .../Meetings/ScheduleMeetingTests.cs | 6 +- .../Meetings/CreateMeetingTests.cs | 2 +- .../CreateTentativeReservationTests.cs | 8 +-- 12 files changed, 123 insertions(+), 53 deletions(-) create mode 100644 Core.Testing/TestWebHostBuilder.cs diff --git a/Core.Testing/ApiFixture.cs b/Core.Testing/ApiFixture.cs index b036282cd..b69738634 100644 --- a/Core.Testing/ApiFixture.cs +++ b/Core.Testing/ApiFixture.cs @@ -1,39 +1,53 @@ +using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Core.Events; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Core.Testing { - public abstract class ApiFixture: IAsyncLifetime where TStartup : class + public abstract class ApiFixture: ApiFixture where TStartup : class { - protected readonly TestContext Sut; + public override TestContext CreateTestContext() => + new TestContext(GetConfiguration, SetupServices, SetupWebHostBuilder); + } + + public abstract class ApiFixture: IAsyncLifetime + { + protected readonly TestContext Sut; private HttpClient Client => Sut.Client; protected abstract string ApiUrl { get; } - protected virtual Dictionary GetConfiguration(string fixtureName) - => new Dictionary(); + protected virtual Dictionary GetConfiguration(string fixtureName) => new(); + + protected virtual Action? SetupServices => null; + + protected virtual Func? SetupWebHostBuilder => null; protected ApiFixture() { - Sut = new TestContext(GetConfiguration); + Sut = CreateTestContext(); } + public virtual TestContext CreateTestContext() => new(GetConfiguration, SetupServices, SetupWebHostBuilder); + public virtual Task InitializeAsync() => Task.CompletedTask; public virtual Task DisposeAsync() => Task.CompletedTask; - public Task GetAsync(string path = "") + public Task Get(string path = "") { return Client.GetAsync( $"{ApiUrl}/{path}" ); } - protected Task PostAsync(string path, object request) + public Task Post(string path, object request) { return Client.PostAsync( $"{ApiUrl}/{path}", @@ -41,15 +55,15 @@ protected Task PostAsync(string path, object request) ); } - protected Task PostAsync(object request) + public Task Post(object request) { - return PostAsync(string.Empty, request); + return Post(string.Empty, request); } - public IReadOnlyCollection PublishedExternalEventsOfType() where TEvent: IExternalEvent + public IReadOnlyCollection PublishedExternalEventsOfType() where TEvent : IExternalEvent => Sut.PublishedExternalEventsOfType(); - public IReadOnlyCollection PublishedInternalEventsOfType() where TEvent: IEvent + public IReadOnlyCollection PublishedInternalEventsOfType() where TEvent : IEvent => Sut.PublishedInternalEventsOfType(); } } diff --git a/Core.Testing/TestContext.cs b/Core.Testing/TestContext.cs index b827fd21f..02408064d 100644 --- a/Core.Testing/TestContext.cs +++ b/Core.Testing/TestContext.cs @@ -18,8 +18,27 @@ namespace Core.Testing { - public class TestContext: IDisposable + public class TestContext: TestContext where TStartup : class + { + public TestContext( + Func>? getConfiguration = null, + Action? setupServices = null, + Func? setupWebHostBuilder = null + ): base(getConfiguration, setupServices, (webHostBuilder => + { + SetupWebHostBuilder(webHostBuilder); + setupWebHostBuilder?.Invoke(webHostBuilder); + return webHostBuilder; + })) + { + } + + private static IWebHostBuilder SetupWebHostBuilder(IWebHostBuilder webHostBuilder) + => webHostBuilder.UseStartup(); + } + + public class TestContext: IDisposable { public HttpClient Client { get; } @@ -29,47 +48,50 @@ public class TestContext: IDisposable private readonly DummyExternalEventProducer externalEventProducer = new(); private readonly DummyExternalCommandBus externalCommandBus = new(); - private readonly Func> getConfiguration = _ => new Dictionary(); + private readonly Func> getConfiguration = + _ => new Dictionary(); - public TestContext(Func>? getConfiguration = null) + public TestContext( + Func>? getConfiguration = null, + Action? setupServices = null, + Func? setupWebHostBuilder = null + ) { if (getConfiguration != null) { this.getConfiguration = getConfiguration; } + var fixtureName = new StackTrace().GetFrame(3)!.GetMethod()!.DeclaringType!.Name; var configuration = this.getConfiguration(fixtureName); - var projectDir = Directory.GetCurrentDirectory(); - - server = new TestServer(new WebHostBuilder() - .UseEnvironment("Tests") - .UseContentRoot(projectDir) - .UseConfiguration(new ConfigurationBuilder() - .SetBasePath(projectDir) - .AddJsonFile("appsettings.json", true) - .AddInMemoryCollection(configuration) - .Build() - ) - .ConfigureServices(services => - { - services.AddSingleton(eventsLog); - services.AddSingleton(typeof(INotificationHandler<>), typeof(EventListener<>)); - services.AddSingleton(externalEventProducer); - services.AddSingleton(externalCommandBus); - services.AddSingleton(); - }) - .UseStartup()); + + setupWebHostBuilder ??= webHostBuilder => webHostBuilder; + server = new TestServer(setupWebHostBuilder(TestWebHostBuilder.Create(configuration, services => + { + ConfigureTestServices(services); + setupServices?.Invoke(services); + }))); + Client = server.CreateClient(); } - public IReadOnlyCollection PublishedExternalEventsOfType() where TEvent: IExternalEvent + protected void ConfigureTestServices(IServiceCollection services) + { + services.AddSingleton(eventsLog); + services.AddSingleton(typeof(INotificationHandler<>), typeof(EventListener<>)); + services.AddSingleton(externalEventProducer); + services.AddSingleton(externalCommandBus); + services.AddSingleton(); + } + + public IReadOnlyCollection PublishedExternalEventsOfType() where TEvent : IExternalEvent { return externalEventProducer.PublishedEvents.OfType().ToList(); } - public IReadOnlyCollection PublishedExternalCommandOfType() where TCommand: ICommand + public IReadOnlyCollection PublishedExternalCommandOfType() where TCommand : ICommand { return externalCommandBus.SentCommands.OfType().ToList(); } diff --git a/Core.Testing/TestWebHostBuilder.cs b/Core.Testing/TestWebHostBuilder.cs new file mode 100644 index 000000000..0fe4ce1f2 --- /dev/null +++ b/Core.Testing/TestWebHostBuilder.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Core.Events.External; +using Core.Requests; +using MediatR; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Shipments.Api.Tests.Core; + +namespace Core.Testing +{ + public static class TestWebHostBuilder + { + public static IWebHostBuilder Create(Dictionary configuration, Action? configureServices = null) + { + var projectDir = Directory.GetCurrentDirectory(); + configureServices ??= _ => { }; + + return new WebHostBuilder() + .UseEnvironment("Tests") + .UseContentRoot(projectDir) + .UseConfiguration(new ConfigurationBuilder() + .SetBasePath(projectDir) + .AddJsonFile("appsettings.json", true) + .AddInMemoryCollection(configuration) + .Build() + ) + .ConfigureServices(configureServices); + } + } +} diff --git a/Sample/BankAccounts/EventSourcing.Sample.Web/Startup.cs b/Sample/BankAccounts/EventSourcing.Sample.Web/Startup.cs index 27e024197..8d260dcc9 100644 --- a/Sample/BankAccounts/EventSourcing.Sample.Web/Startup.cs +++ b/Sample/BankAccounts/EventSourcing.Sample.Web/Startup.cs @@ -121,7 +121,8 @@ private void ConfigureMarten(IServiceCollection services) private void ConfigureEf(IServiceCollection services) { - services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("ClientsDatabase"))); + services.AddDbContext( + options => options.UseNpgsql(Configuration.GetConnectionString("ClientsDatabase"))); } public void Configure(IApplicationBuilder app) diff --git a/Sample/ECommerce/Carts/Carts.Api.Tests/Carts/InitCartTests.cs b/Sample/ECommerce/Carts/Carts.Api.Tests/Carts/InitCartTests.cs index d1afa5199..261bc9caa 100644 --- a/Sample/ECommerce/Carts/Carts.Api.Tests/Carts/InitCartTests.cs +++ b/Sample/ECommerce/Carts/Carts.Api.Tests/Carts/InitCartTests.cs @@ -23,7 +23,7 @@ public class InitCartFixture: ApiFixture public override async Task InitializeAsync() { - CommandResponse = await PostAsync(new InitCartRequest {ClientId = ClientId }); + CommandResponse = await Post(new InitCartRequest {ClientId = ClientId }); } } @@ -78,7 +78,7 @@ public async Task CreateCommand_ShouldCreate_Cart() var query = $"{createdId}"; //send query - var queryResponse = await fixture.GetAsync(query); + var queryResponse = await fixture.Get(query); queryResponse.EnsureSuccessStatusCode(); var queryResult = await queryResponse.Content.ReadAsStringAsync(); diff --git a/Sample/ECommerce/Orders/Orders.Api.Tests/Carts/InitCartTests.cs b/Sample/ECommerce/Orders/Orders.Api.Tests/Carts/InitCartTests.cs index c3de58b09..833709ad1 100644 --- a/Sample/ECommerce/Orders/Orders.Api.Tests/Carts/InitCartTests.cs +++ b/Sample/ECommerce/Orders/Orders.Api.Tests/Carts/InitCartTests.cs @@ -33,7 +33,7 @@ public class InitOrderFixture: ApiFixture public override async Task InitializeAsync() { - CommandResponse = await PostAsync(new InitOrderRequest( + CommandResponse = await Post(new InitOrderRequest( ClientId, ProductItems, TotalPrice diff --git a/Sample/ECommerce/Payments/Payments.Api.Tests/Payments/RequestPaymentsTests.cs b/Sample/ECommerce/Payments/Payments.Api.Tests/Payments/RequestPaymentsTests.cs index cd19fd7d6..a2707e5c0 100644 --- a/Sample/ECommerce/Payments/Payments.Api.Tests/Payments/RequestPaymentsTests.cs +++ b/Sample/ECommerce/Payments/Payments.Api.Tests/Payments/RequestPaymentsTests.cs @@ -25,7 +25,7 @@ public class RequestPaymentsTestsFixture: ApiFixture public override async Task InitializeAsync() { - CommandResponse = await PostAsync(new RequestPaymentRequest {OrderId = OrderId, Amount = Amount}); + CommandResponse = await Post(new RequestPaymentRequest {OrderId = OrderId, Amount = Amount}); } } diff --git a/Sample/ECommerce/Shipments/Shipments.Api.Tests/Packages/SendPackageTests.cs b/Sample/ECommerce/Shipments/Shipments.Api.Tests/Packages/SendPackageTests.cs index 137ae3dfb..93668869f 100644 --- a/Sample/ECommerce/Shipments/Shipments.Api.Tests/Packages/SendPackageTests.cs +++ b/Sample/ECommerce/Shipments/Shipments.Api.Tests/Packages/SendPackageTests.cs @@ -41,7 +41,7 @@ public class SendPackageFixture: ApiFixture public override async Task InitializeAsync() { - CommandResponse = await PostAsync(new SendPackage(OrderId, ProductItems)); + CommandResponse = await Post(new SendPackage(OrderId, ProductItems)); } } @@ -100,7 +100,7 @@ public async Task CreateCommand_ShouldCreate_Package() var query = $"{createdId}"; //send query - var queryResponse = await fixture.GetAsync(query); + var queryResponse = await fixture.Get(query); queryResponse.EnsureSuccessStatusCode(); var queryResult = await queryResponse.Content.ReadAsStringAsync(); diff --git a/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/Meetings/CreateMeetingTests.cs b/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/Meetings/CreateMeetingTests.cs index 73e27755d..02388f571 100644 --- a/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/Meetings/CreateMeetingTests.cs +++ b/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/Meetings/CreateMeetingTests.cs @@ -31,7 +31,7 @@ public override async Task InitializeAsync() ); // send create command - CommandResponse = await PostAsync(command); + CommandResponse = await Post(command); } } @@ -80,7 +80,7 @@ public async Task CreateCommand_ShouldUpdateReadModel() var query = $"{fixture.MeetingId}"; //send query - var queryResponse = await fixture.GetAsync(query); + var queryResponse = await fixture.Get(query); queryResponse.EnsureSuccessStatusCode(); var queryResult = await queryResponse.Content.ReadAsStringAsync(); diff --git a/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/Meetings/ScheduleMeetingTests.cs b/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/Meetings/ScheduleMeetingTests.cs index 74a034e47..19e169d9a 100644 --- a/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/Meetings/ScheduleMeetingTests.cs +++ b/Sample/MeetingsManagement/MeetingsManagement.IntegrationTests/Meetings/ScheduleMeetingTests.cs @@ -34,12 +34,12 @@ public override async Task InitializeAsync() ); // send create command - await PostAsync( createCommand); + await Post( createCommand); var occurs = DateRange.Create(Start, End); // send schedule meeting request - CommandResponse = await PostAsync($"{MeetingId}/schedule", occurs); + CommandResponse = await Post($"{MeetingId}/schedule", occurs); } } @@ -73,7 +73,7 @@ public async Task ScheduleMeeting_ShouldUpdateReadModel() var query = new GetMeeting(fixture.MeetingId); //send query - var queryResponse = await fixture.GetAsync($"{fixture.MeetingId}"); + var queryResponse = await fixture.Get($"{fixture.MeetingId}"); queryResponse.EnsureSuccessStatusCode(); var queryResult = await queryResponse.Content.ReadAsStringAsync(); diff --git a/Sample/MeetingsManagement/MeetingsSearch.IntegrationTests/Meetings/CreateMeetingTests.cs b/Sample/MeetingsManagement/MeetingsSearch.IntegrationTests/Meetings/CreateMeetingTests.cs index eaa4219d5..ca9a6bdc4 100644 --- a/Sample/MeetingsManagement/MeetingsSearch.IntegrationTests/Meetings/CreateMeetingTests.cs +++ b/Sample/MeetingsManagement/MeetingsSearch.IntegrationTests/Meetings/CreateMeetingTests.cs @@ -43,7 +43,7 @@ public CreateMeetingTests(CreateMeetingFixture fixture) public async Task MeetingCreated_ShouldUpdateReadModel() { //send query - var queryResponse = await fixture.GetAsync($"{MeetingsSearchApi.MeetingsUrl}"); + var queryResponse = await fixture.Get($"{MeetingsSearchApi.MeetingsUrl}"); queryResponse.EnsureSuccessStatusCode(); var queryResult = await queryResponse.Content.ReadAsStringAsync(); diff --git a/Sample/Tickets/Tickets.Api.Tests/Reservations/CreateTentativeReservationTests.cs b/Sample/Tickets/Tickets.Api.Tests/Reservations/CreateTentativeReservationTests.cs index c5d6d0966..12e9a6161 100644 --- a/Sample/Tickets/Tickets.Api.Tests/Reservations/CreateTentativeReservationTests.cs +++ b/Sample/Tickets/Tickets.Api.Tests/Reservations/CreateTentativeReservationTests.cs @@ -31,7 +31,7 @@ protected override Dictionary GetConfiguration(string fixtureNam public override async Task InitializeAsync() { // send create command - CommandResponse = await PostAsync(new CreateTentativeReservationRequest {SeatId = SeatId}); + CommandResponse = await Post(new CreateTentativeReservationRequest {SeatId = SeatId}); } } @@ -86,7 +86,7 @@ public async Task CreateCommand_ShouldCreate_ReservationDetailsReadModel() var query = $"{createdReservationId}"; //send query - var queryResponse = await fixture.GetAsync(query); + var queryResponse = await fixture.Get(query); queryResponse.EnsureSuccessStatusCode(); var queryResult = await queryResponse.Content.ReadAsStringAsync(); @@ -105,7 +105,7 @@ public async Task CreateCommand_ShouldCreate_ReservationList() var createdReservationId = await fixture.CommandResponse.GetResultFromJson(); //send query - var queryResponse = await fixture.GetAsync(); + var queryResponse = await fixture.Get(); queryResponse.EnsureSuccessStatusCode(); var queryResult = await queryResponse.Content.ReadAsStringAsync(); @@ -138,7 +138,7 @@ public async Task CreateCommand_ShouldCreate_ReservationHistory() var query = $"{createdReservationId}/history"; //send query - var queryResponse = await fixture.GetAsync(query); + var queryResponse = await fixture.Get(query); queryResponse.EnsureSuccessStatusCode(); var queryResult = await queryResponse.Content.ReadAsStringAsync();