Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use InMemoryEventBus as scoped and use InMemoryEventBus in integration tests #99

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.Events.EventBus.InMemory;

using Fitnet.Common.Events;
using Fitnet.Common.Events.EventBus;

internal sealed class NotificationDecorator<TNotification>(IEventBus eventBus, INotificationHandler<TNotification>? innerHandler)
: INotificationHandler<TNotification>
where TNotification : INotification
{
public async Task Handle(TNotification notification, CancellationToken cancellationToken)
{
var @event = notification as IIntegrationEvent;
await eventBus.PublishAsync(@event!, cancellationToken);

if (innerHandler is not null)
{
await innerHandler.Handle(notification, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine.Configuration;

using System.Reflection;
using Events.EventBus.InMemory;
using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus;
using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus.InMemory;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Time.Testing;

internal static class ConfigurationExtensions
{
Expand Down Expand Up @@ -41,10 +39,73 @@ internal static WebApplicationFactory<T> SetFakeSystemClock<T>(this WebApplicati
internal static WebApplicationFactory<T> WithFakeEventBus<T>(this WebApplicationFactory<T> webApplicationFactory, IEventBus eventBusMock)
where T : class =>
webApplicationFactory.WithWebHostBuilder(webHostBuilder => webHostBuilder.ConfigureTestServices(services =>
services.AddSingleton(eventBusMock)));
{
AddNotificationDecorator(eventBusMock, services, true);
}));

internal static WebApplicationFactory<T> WithoutEventHandlers<T>(this WebApplicationFactory<T> webApplicationFactory, IEventBus eventBusMock)
where T : class =>
webApplicationFactory.WithWebHostBuilder(webHostBuilder => webHostBuilder.ConfigureTestServices(services =>
{
AddNotificationDecorator(eventBusMock, services, false);
}));

internal static WebApplicationFactory<T> WithFakeConsumers<T>(this WebApplicationFactory<T> webApplicationFactory)
where T : class =>
webApplicationFactory.WithWebHostBuilder(webHostBuilder => webHostBuilder.ConfigureTestServices(services =>
services.AddInMemoryEventBus(Assembly.GetExecutingAssembly())));
{
services.AddInMemoryEventBus(Assembly.GetExecutingAssembly());
var decorator = services.FirstOrDefault(s => s.ImplementationType == typeof(NotificationDecorator<>));
if (decorator is not null)
{
services.Remove(decorator);
}
}));

private static void AddNotificationDecorator(IEventBus eventBusMock, IServiceCollection services, bool hasSingleEventHandling)
{
var notificationHandlerServices = services.Where(s =>
s.ServiceType.IsGenericType &&
s.ServiceType.GetGenericTypeDefinition() == typeof(INotificationHandler<>))
.ToList();

foreach (var notificationHandlerService in notificationHandlerServices)
{
var serviceType = notificationHandlerService.ServiceType;
var implementationType = notificationHandlerService.ImplementationType;

services.Remove(notificationHandlerService);
services.AddTransient(implementationType!);

services.AddTransient(serviceType, serviceProvider =>
{
var notificationHandler = hasSingleEventHandling
? NotificationHandlerWithFakeEventBus(eventBusMock, implementationType, serviceProvider)
: null;

var decoratorType = typeof(NotificationDecorator<>).MakeGenericType(serviceType.GenericTypeArguments[0]);
return Activator.CreateInstance(decoratorType, eventBusMock, notificationHandler)!;
});
}
}

private static object NotificationHandlerWithFakeEventBus(IEventBus eventBusMock, Type? implementationType,
IServiceProvider serviceProvider)
{
var constructor = implementationType!.GetConstructors().FirstOrDefault()!;
var parameters = constructor.GetParameters();
var handlerDependencies = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
if (parameters[i].ParameterType == typeof(IEventBus))
{
handlerDependencies[i] = eventBusMock;
continue;
}
handlerDependencies[i] = serviceProvider.GetService(parameters[i]!.ParameterType)!;
}

var notificationHandler = Activator.CreateInstance(implementationType, handlerDependencies)!;
return notificationHandler;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine.IntegrationEvents.Handlers;

using EvolutionaryArchitecture.Fitnet.Common.Events;
using MediatR;

internal sealed class IntegrationEventHandlerScope<TIntegrationEvent> : IDisposable
where TIntegrationEvent : IIntegrationEvent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Contracts.PrepareCont
using EvolutionaryArchitecture.Fitnet.Contracts.PrepareContract;
using Common.TestEngine;
using Common.TestEngine.Configuration;
using Microsoft.AspNetCore.Mvc;

public sealed class PrepareContractTests(
WebApplicationFactory<Program> applicationInMemoryFactory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Contracts.SignContrac
using Common.TestEngine.Configuration;
using Fitnet.Contracts.SignContract.Events;
using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus;
using Microsoft.AspNetCore.Mvc;

public sealed class SignContractTests : IClassFixture<WebApplicationFactory<Program>>, IClassFixture<DatabaseContainer>
{
Expand All @@ -17,7 +16,7 @@ public sealed class SignContractTests : IClassFixture<WebApplicationFactory<Prog
public SignContractTests(WebApplicationFactory<Program> applicationInMemoryFactory,
DatabaseContainer database) =>
_applicationHttpClient = applicationInMemoryFactory
.WithFakeEventBus(_fakeEventBus)
.WithoutEventHandlers(_fakeEventBus)
.WithContainerDatabaseConfigured(database.ConnectionString!)
.CreateClient();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
global using System.Collections;
global using System.Net;
global using FluentAssertions;
global using Xunit;
global using MediatR;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore.Mvc.Testing;
global using Microsoft.AspNetCore.TestHost;
global using Microsoft.Extensions.Time.Testing;
global using Bogus;
global using JetBrains.Annotations;
global using NSubstitute;
global using EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine;
global using EvolutionaryArchitecture.Fitnet.IntegrationTests.Common.TestEngine;
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Offers.Prepare;

using Common.TestEngine.Configuration;
using Common.TestEngine.IntegrationEvents.Handlers;
using Fitnet.Offers.Prepare;
using Fitnet.Passes.MarkPassAsExpired.Events;
using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus;


public sealed class PrepareOfferTests : IClassFixture<WebApplicationFactory<Program>>,
IClassFixture<DatabaseContainer>
{
Expand All @@ -27,18 +24,16 @@ public PrepareOfferTests(WebApplicationFactory<Program> applicationInMemoryFacto
internal async Task Given_pass_expired_event_published_Then_new_offer_should_be_prepared()
{
// Arrange
using var integrationEventHandlerScope =
new IntegrationEventHandlerScope<PassExpiredEvent>(_applicationInMemory);
var integrationEventHandler = integrationEventHandlerScope.IntegrationEventHandler;
using var serviceScope = _applicationInMemory.Services.CreateScope();
var eventBus = serviceScope.ServiceProvider.GetRequiredService<IEventBus>();
var @event = PassExpiredEventFaker.CreateValid();

// Act
await integrationEventHandler.Handle(@event, CancellationToken.None);
await eventBus.PublishAsync(@event);

// Assert
EnsureThatOfferPreparedEventWasPublished();
}

private void EnsureThatOfferPreparedEventWasPublished() => _fakeEventBus.Received(1)
.PublishAsync(Arg.Any<OfferPrepareEvent>(), Arg.Any<CancellationToken>());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Passes.MarkPassAsExpi
using Fitnet.Passes;
using RegisterPass;
using Common.TestEngine.Configuration;
using Common.TestEngine.IntegrationEvents.Handlers;
using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus;
using Fitnet.Contracts.SignContract.Events;
using Fitnet.Passes.GetAllPasses;
Expand Down Expand Up @@ -74,9 +73,9 @@ internal async Task Given_mark_pass_as_expired_request_with_not_existing_id_Then
private async Task<ContractSignedEvent> RegisterPass()
{
var @event = ContractSignedEventFaker.Create();
using var integrationEventHandlerScope =
new IntegrationEventHandlerScope<ContractSignedEvent>(_applicationInMemoryFactory);
await integrationEventHandlerScope.Consume(@event);
using var serviceScope = _applicationInMemoryFactory.Services.CreateScope();
var eventBus = serviceScope.ServiceProvider.GetRequiredService<IEventBus>();
await eventBus.PublishAsync(@event);

return @event;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Passes.RegisterPass;

using Common.TestEngine.Configuration;
using Common.TestEngine.IntegrationEvents.Handlers;
using Fitnet.Contracts.SignContract.Events;
using Fitnet.Passes.RegisterPass.Events;
using EvolutionaryArchitecture.Fitnet.Common.Events.EventBus;

Expand All @@ -25,12 +23,12 @@ public RegisterPassTests(WebApplicationFactory<Program> applicationInMemoryFacto
internal async Task Given_contract_signed_event_Then_should_register_pass()
{
// Arrange
using var integrationEventHandlerScope =
new IntegrationEventHandlerScope<ContractSignedEvent>(_applicationInMemory);
using var serviceScope = _applicationInMemory.Services.CreateScope();
var eventBus = serviceScope.ServiceProvider.GetRequiredService<IEventBus>();
var @event = ContractSignedEventFaker.Create();

// Act
await integrationEventHandlerScope.Consume(@event);
await eventBus.PublishAsync(@event);

// Assert
EnsureThatPassRegisteredEventWasPublished();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
namespace EvolutionaryArchitecture.Fitnet.IntegrationTests.Reports.GenerateNewPassesPerMonthReport;

using Common.TestEngine.Configuration;
using Common.TestEngine.IntegrationEvents.Handlers;
using Fitnet.Contracts.SignContract.Events;
using Fitnet.Common.Events.EventBus;
using Fitnet.Reports;
using Fitnet.Reports.GenerateNewPassesRegistrationsPerMonthReport.Dtos;
using Passes.RegisterPass;
Expand Down Expand Up @@ -51,10 +50,11 @@ private async Task RegisterPasses(List<PassRegistrationDateRange> reportTestData

private async Task RegisterPass(DateTimeOffset from, DateTimeOffset to)
{
using var integrationEventHandlerScope =
new IntegrationEventHandlerScope<ContractSignedEvent>(_applicationInMemoryFactory);
var integrationEventHandler = integrationEventHandlerScope.IntegrationEventHandler;
using var serviceScope = _applicationInMemoryFactory.Services.CreateScope();
var eventBus = serviceScope.ServiceProvider.GetRequiredService<IEventBus>();
var @event = ContractSignedEventFaker.Create(from, to);
await integrationEventHandler.Handle(@event, CancellationToken.None);

// Act
await eventBus.PublishAsync(@event);
}
}