Skip to content

Commit

Permalink
Merge pull request #65 from meysamhadeli/develop
Browse files Browse the repository at this point in the history
- add test container for RabbitMq in test base
  • Loading branch information
meysamhadeli authored Nov 27, 2022
2 parents de35d34 + b4d475a commit 68bb28d
Show file tree
Hide file tree
Showing 29 changed files with 226 additions and 170 deletions.
104 changes: 59 additions & 45 deletions src/BuildingBlocks/MassTransit/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,62 +18,76 @@ public static class Extensions
bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER"), out var inContainer) &&
inContainer;

public static IServiceCollection AddCustomMassTransit(this IServiceCollection services, Assembly assembly, IWebHostEnvironment env)
public static IServiceCollection AddCustomMassTransit(this IServiceCollection services, Assembly assembly,
IWebHostEnvironment env)
{
if (!env.IsEnvironment("test"))
if (env.IsEnvironment("test"))
{
services.AddMassTransitTestHarness(configure =>
{
SetupMasstransitConfigurations(services, assembly, configure);
});
}
else
{
services.AddMassTransit(configure =>
{
configure.AddConsumers(assembly);
SetupMasstransitConfigurations(services, assembly, configure);
});
}

configure.UsingRabbitMq((context, configurator) =>
{
var rabbitMqOptions = services.GetOptions<RabbitMqOptions>("RabbitMq");
var host = IsRunningInContainer ? "rabbitmq" : rabbitMqOptions.HostName;
return services;
}

configurator.Host(host, h =>
{
h.Username(rabbitMqOptions.UserName);
h.Password(rabbitMqOptions.Password);
});
private static void SetupMasstransitConfigurations(IServiceCollection services, Assembly assembly,
IBusRegistrationConfigurator configure)
{
configure.AddConsumers(assembly);

var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes())
.Where(x => x.IsAssignableTo(typeof(IIntegrationEvent))
&& !x.IsInterface
&& !x.IsAbstract
&& !x.IsGenericType);
configure.UsingRabbitMq((context, configurator) =>
{
var rabbitMqOptions = services.GetOptions<RabbitMqOptions>("RabbitMq");
var host = IsRunningInContainer ? "rabbitmq" : rabbitMqOptions.HostName;

configurator.Host(host, rabbitMqOptions?.Port ?? 5672, "/", h =>
{
h.Username(rabbitMqOptions.UserName);
h.Password(rabbitMqOptions.Password);
});

foreach (var type in types)
{
var consumers = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes())
.Where(x => x.IsAssignableTo(typeof(IConsumer<>).MakeGenericType(type))).ToList();
var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes())
.Where(x => x.IsAssignableTo(typeof(IIntegrationEvent))
&& !x.IsInterface
&& !x.IsAbstract
&& !x.IsGenericType);

if (consumers.Any())
configurator.ReceiveEndpoint(
string.IsNullOrEmpty(rabbitMqOptions.ExchangeName)
? type.Name.Underscore()
: $"{rabbitMqOptions.ExchangeName}_{type.Name.Underscore()}", e =>
{
e.UseConsumeFilter(typeof(ConsumeFilter<>), context); //generic filter
foreach (var type in types)
{
var consumers = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes())
.Where(x => x.IsAssignableTo(typeof(IConsumer<>).MakeGenericType(type))).ToList();

foreach (var consumer in consumers)
{
configurator.ConfigureEndpoints(context, x => x.Exclude(consumer));
var methodInfo = typeof(DependencyInjectionReceiveEndpointExtensions)
.GetMethods()
.Where(x => x.GetParameters()
.Any(p => p.ParameterType == typeof(IServiceProvider)))
.FirstOrDefault(x => x.Name == "Consumer" && x.IsGenericMethod);
if (consumers.Any())
configurator.ReceiveEndpoint(
string.IsNullOrEmpty(rabbitMqOptions.ExchangeName)
? type.Name.Underscore()
: $"{rabbitMqOptions.ExchangeName}_{type.Name.Underscore()}", e =>
{
e.UseConsumeFilter(typeof(ConsumeFilter<>), context); //generic filter

var generic = methodInfo?.MakeGenericMethod(consumer);
generic?.Invoke(e, new object[] {e, context, null});
}
});
}
});
});
}
foreach (var consumer in consumers)
{
configurator.ConfigureEndpoints(context, x => x.Exclude(consumer));
var methodInfo = typeof(DependencyInjectionReceiveEndpointExtensions)
.GetMethods()
.Where(x => x.GetParameters()
.Any(p => p.ParameterType == typeof(IServiceProvider)))
.FirstOrDefault(x => x.Name == "Consumer" && x.IsGenericMethod);

return services;
var generic = methodInfo?.MakeGenericMethod(consumer);
generic?.Invoke(e, new object[] {e, context, null});
}
});
}
});
}
}
3 changes: 2 additions & 1 deletion src/BuildingBlocks/MassTransit/RabbitMqOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public class RabbitMqOptions
public string ExchangeName { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
public ushort? Port { get; set; }
}
116 changes: 77 additions & 39 deletions src/BuildingBlocks/TestBase/IntegrationTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ public class IntegrationTestFixture<TEntryPoint> : IDisposable
{
private readonly WebApplicationFactory<TEntryPoint> _factory;

private int Timeout => 180;

private int Timeout => 60; // Second
private ITestHarness TestHarness => ServiceProvider.GetTestHarness();
public HttpClient HttpClient => _factory.CreateClient();
public ITestHarness TestHarness => ServiceProvider.GetTestHarness();

public GrpcChannel Channel =>
GrpcChannel.ForAddress(HttpClient.BaseAddress!, new GrpcChannelOptions {HttpClient = HttpClient});
Expand All @@ -46,6 +47,7 @@ public class IntegrationTestFixture<TEntryPoint> : IDisposable
public MsSqlTestcontainer SqlTestContainer;
public MsSqlTestcontainer SqlPersistTestContainer;
public MongoDbTestcontainer MongoTestContainer;
public RabbitMqTestcontainer RabbitMqTestContainer;

public IntegrationTestFixture()
{
Expand All @@ -57,21 +59,6 @@ public IntegrationTestFixture()
{
TestRegistrationServices?.Invoke(services);
services.ReplaceSingleton(AddHttpContextAccessorMock);
services.AddMassTransitTestHarness(x =>
{
x.UsingRabbitMq((context, cfg) =>
{
var rabbitMqOptions = services.GetOptions<RabbitMqOptions>("RabbitMq");
var host = rabbitMqOptions.HostName;

cfg.Host(host, h =>
{
h.Username(rabbitMqOptions.UserName);
h.Password(rabbitMqOptions.Password);
});
cfg.ConfigureEndpoints(context);
});
});
});
});
}
Expand Down Expand Up @@ -134,6 +121,36 @@ public Task SendAsync(IRequest request)
});
}

public async Task Publish<TMessage>(TMessage message, CancellationToken cancellationToken = default)
where TMessage : class, IEvent
{
await TestHarness.Bus.Publish<TMessage>(message, cancellationToken);
}

public async Task WaitForPublishing<TMessage>(CancellationToken cancellationToken = default)
where TMessage : class, IEvent
{
await WaitUntilConditionMet(async () =>
{
var published = await TestHarness.Published.Any<TMessage>(cancellationToken);
var faulty = await TestHarness.Published.Any<Fault<TMessage>>(cancellationToken);

return published && faulty == false;
});
}

public async Task WaitForConsuming<TMessage>(CancellationToken cancellationToken = default)
where TMessage : class, IEvent
{
await WaitUntilConditionMet(async () =>
{
var consumed = await TestHarness.Consumed.Any<TMessage>(cancellationToken);
var faulty = await TestHarness.Consumed.Any<Fault<TMessage>>(cancellationToken);

return consumed && faulty == false;
});
}

// Ref: https://tech.energyhelpline.com/in-memory-testing-with-masstransit/
public async ValueTask WaitUntilConditionMet(Func<Task<bool>> conditionToMet, int? timeoutSecond = null)
{
Expand Down Expand Up @@ -336,6 +353,9 @@ private string MongoConnectionString
set => Fixture.ServiceProvider.GetRequiredService<IOptions<MongoOptions>>().Value.ConnectionString = value;
}

private RabbitMqOptions RabbitMqOptions =>
Fixture.ServiceProvider.GetRequiredService<IOptions<RabbitMqOptions>>()?.Value;

public IntegrationTestFixtureCore(IntegrationTestFixture<TEntryPoint> integrationTestFixture)
{
Fixture = integrationTestFixture;
Expand All @@ -349,48 +369,66 @@ public async Task InitializeAsync()
_checkpointDefaultDB = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
_checkpointPersistMessageDB = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};

if (!string.IsNullOrEmpty(DefaultConnectionString))
await _checkpointDefaultDB.Reset(DefaultConnectionString);

if (!string.IsNullOrEmpty(PersistConnectionString))
await _checkpointPersistMessageDB.Reset(PersistConnectionString);

_mongoRunner = MongoDbRunner.Start();

if (MongoConnectionString != null)
MongoConnectionString = _mongoRunner.ConnectionString;

// <<For using test-container base>>
// Fixture.SqlTestContainer = TestContainers.SqlTestContainer;
// Fixture.SqlPersistTestContainer = TestContainers.SqlPersistTestContainer;
// Fixture.MongoTestContainer = TestContainers.MongoTestContainer;
//
// await Fixture.SqlTestContainer.StartAsync();
// await Fixture.SqlPersistTestContainer.StartAsync();
// await Fixture.MongoTestContainer.StartAsync();
//
// DefaultConnectionString = Fixture.SqlTestContainer?.ConnectionString;
// PersistConnectionString = Fixture.SqlPersistTestContainer?.ConnectionString;
// MongoConnectionString = Fixture.MongoTestContainer?.ConnectionString;
//await StartTestContainerAsync();

await SeedDataAsync();
}

public async Task DisposeAsync()
{
if (!string.IsNullOrEmpty(DefaultConnectionString))
await _checkpointDefaultDB.Reset(DefaultConnectionString);

if (!string.IsNullOrEmpty(PersistConnectionString))
await _checkpointPersistMessageDB.Reset(PersistConnectionString);

if (!string.IsNullOrEmpty(PersistConnectionString))
_mongoRunner.Dispose();

// <<For using test-container base>>
// await Fixture.SqlTestContainer.StopAsync();
// await Fixture.SqlPersistTestContainer.StopAsync();
// await Fixture.MongoTestContainer.StopAsync();
//await StopTestContainerAsync();
}

protected virtual void RegisterTestsServices(IServiceCollection services)
{
}

private async Task StartTestContainerAsync()
{
// <<For using test-container base>>
Fixture.SqlTestContainer = TestContainers.SqlTestContainer;
Fixture.SqlPersistTestContainer = TestContainers.SqlPersistTestContainer;
Fixture.MongoTestContainer = TestContainers.MongoTestContainer;
Fixture.RabbitMqTestContainer = TestContainers.RabbitMqTestContainer;

await Fixture.SqlTestContainer.StartAsync();
await Fixture.SqlPersistTestContainer.StartAsync();
await Fixture.MongoTestContainer.StartAsync();
await Fixture.RabbitMqTestContainer.StartAsync();

DefaultConnectionString = Fixture.SqlTestContainer?.ConnectionString;
PersistConnectionString = Fixture.SqlPersistTestContainer?.ConnectionString;
MongoConnectionString = Fixture.MongoTestContainer?.ConnectionString;

RabbitMqOptions.Password = Fixture.RabbitMqTestContainer.Password;
RabbitMqOptions.UserName = Fixture.RabbitMqTestContainer.Username;
RabbitMqOptions.HostName = Fixture.RabbitMqTestContainer.Hostname;
RabbitMqOptions.Port = (ushort)Fixture.RabbitMqTestContainer.Port;
}

private async Task StopTestContainerAsync()
{
// <<For using test-container base>>
await Fixture.SqlTestContainer.StopAsync();
await Fixture.SqlPersistTestContainer.StopAsync();
await Fixture.MongoTestContainer.StopAsync();
await Fixture.RabbitMqTestContainer.StopAsync();
}

private async Task SeedDataAsync()
{
using var scope = Fixture.ServiceProvider.CreateScope();
Expand Down
12 changes: 11 additions & 1 deletion src/BuildingBlocks/TestBase/TestContainers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public static class TestContainers
.WithDatabase(
new MsSqlTestcontainerConfiguration
{
Database = Guid.NewGuid().ToString("D"), Password = Guid.NewGuid().ToString("D")
Database = Guid.NewGuid().ToString("D"),
Password = Guid.NewGuid().ToString("D")
})
.WithImage("mcr.microsoft.com/mssql/server:2017-latest")
.Build();
Expand All @@ -32,4 +33,13 @@ public static class TestContainers
})
.WithImage("mongo")
.Build();

public static RabbitMqTestcontainer RabbitMqTestContainer => new TestcontainersBuilder<RabbitMqTestcontainer>()
.WithMessageBroker(new RabbitMqTestcontainerConfiguration()
{
Password = "guest",
Username = "guest"
})
.WithImage("rabbitmq:3-management")
.Build();
}
3 changes: 2 additions & 1 deletion src/Services/Booking/src/Booking.Api/appsettings.docker.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"HostName": "rabbitmq",
"ExchangeName": "booking",
"UserName": "guest",
"Password": "guest"
"Password": "guest",
"Port": 5672
},
"Grpc": {
"FlightAddress": "https://localhost:5003",
Expand Down
3 changes: 2 additions & 1 deletion src/Services/Booking/src/Booking.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"HostName": "localhost",
"ExchangeName": "booking",
"UserName": "guest",
"Password": "guest"
"Password": "guest",
"Port": 5672
},
"Grpc": {
"FlightAddress": "https://localhost:5003",
Expand Down
3 changes: 2 additions & 1 deletion src/Services/Booking/src/Booking.Api/appsettings.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"HostName": "localhost",
"ExchangeName": "booking",
"UserName": "guest",
"Password": "guest"
"Password": "guest",
"Port": 5672
},
"Logging": {
"LogLevel": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ namespace Integration.Test.Booking.Features;

public class CreateBookingTests : IntegrationTestBase<Program, PersistMessageDbContext, BookingReadDbContext>
{
private readonly ITestHarness _testHarness;

public CreateBookingTests(
IntegrationTestFixture<Program, PersistMessageDbContext, BookingReadDbContext> integrationTestFixture) : base(
integrationTestFixture)
{
_testHarness = Fixture.TestHarness;
}

protected override void RegisterTestsServices(IServiceCollection services)
Expand All @@ -51,8 +48,8 @@ public async Task should_create_booking_to_event_store_currectly()

// Assert
response.Should().BeGreaterOrEqualTo(0);
(await _testHarness.Published.Any<Fault<BookingCreated>>()).Should().BeFalse();
(await _testHarness.Published.Any<BookingCreated>()).Should().BeTrue();

await Fixture.WaitForPublishing<BookingCreated>();
}


Expand Down
3 changes: 2 additions & 1 deletion src/Services/Flight/src/Flight.Api/appsettings.docker.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"HostName": "rabbitmq",
"ExchangeName": "flight",
"UserName": "guest",
"Password": "guest"
"Password": "guest",
"Port": 5672
},
"AllowedHosts": "*"
}
Loading

0 comments on commit 68bb28d

Please sign in to comment.