fix: fix ci failed for tests

This commit is contained in:
Meysam Hadeli 2026-02-20 02:10:54 +03:30
parent 3ce312891a
commit 94a22dfc23
3 changed files with 62 additions and 66 deletions

View File

@ -42,8 +42,8 @@ public class TestFixture<TEntryPoint> : IAsyncLifetime
where TEntryPoint : class where TEntryPoint : class
{ {
private readonly WebApplicationFactory<TEntryPoint> _factory; private readonly WebApplicationFactory<TEntryPoint> _factory;
private int Timeout => 300; // Second private int Timeout => 120; // Second
private ITestHarness TestHarness => ServiceProvider?.GetTestHarness(); public ITestHarness TestHarness => ServiceProvider?.GetTestHarness();
private Action<IServiceCollection> TestRegistrationServices { get; set; } private Action<IServiceCollection> TestRegistrationServices { get; set; }
private PostgreSqlContainer PostgresTestcontainer; private PostgreSqlContainer PostgresTestcontainer;
private PostgreSqlContainer PostgresPersistTestContainer; private PostgreSqlContainer PostgresPersistTestContainer;
@ -136,10 +136,14 @@ public class TestFixture<TEntryPoint> : IAsyncLifetime
{ {
CancellationTokenSource = new CancellationTokenSource(); CancellationTokenSource = new CancellationTokenSource();
await StartTestContainerAsync(); await StartTestContainerAsync();
await TestHarness.Start();
} }
public async Task DisposeAsync() public async Task DisposeAsync()
{ {
await TestHarness.Stop();
await StopTestContainerAsync(); await StopTestContainerAsync();
await _factory.DisposeAsync(); await _factory.DisposeAsync();
await CancellationTokenSource.CancelAsync(); await CancellationTokenSource.CancelAsync();
@ -201,83 +205,71 @@ public class TestFixture<TEntryPoint> : IAsyncLifetime
public async Task Publish<TMessage>(TMessage message, CancellationToken cancellationToken = default) public async Task Publish<TMessage>(TMessage message, CancellationToken cancellationToken = default)
where TMessage : class, IEvent where TMessage : class, IEvent
{ {
// Use harness bus to ensure publish happens only after the bus is started.
await TestHarness.Bus.Publish(message, cancellationToken); await TestHarness.Bus.Publish(message, cancellationToken);
} }
public async Task<bool> WaitForPublishing<TMessage>(CancellationToken cancellationToken = default) public async Task<bool> WaitForPublishing<TMessage>(CancellationToken cancellationToken = default)
where TMessage : class, IEvent where TMessage : class, IEvent
{ {
var result = await WaitUntilConditionMet(async () => var result = await WaitUntilConditionMet(
{ async () =>
var published = await TestHarness.Published.Any<TMessage>(cancellationToken); {
var published = await TestHarness.Published.Any<TMessage>(cancellationToken);
return published; return published;
}); },
cancellationToken: cancellationToken
);
return result; return result;
} }
public async Task<bool> WaitForConsuming<TMessage>(CancellationToken cancellationToken = default) public Task<bool> WaitUntilAsync(
where TMessage : class, IEvent Func<Task<bool>> condition,
{ TimeSpan? timeout = null,
var result = await WaitUntilConditionMet(async () => TimeSpan? pollInterval = null,
{
var consumed = await TestHarness.Consumed.Any<TMessage>(cancellationToken);
return consumed;
});
return result;
}
public async Task<bool> ShouldProcessedPersistInternalCommand<TInternalCommand>(
CancellationToken cancellationToken = default CancellationToken cancellationToken = default
) )
where TInternalCommand : class, IInternalCommand
{ {
var result = await WaitUntilConditionMet(async () => var effectiveTimeout = timeout ?? TimeSpan.FromSeconds(Timeout);
{ var effectivePollInterval = pollInterval ?? TimeSpan.FromMilliseconds(200);
return await ExecuteScopeAsync(async sp =>
return WaitUntilConditionMet(
conditionToMet: async () =>
{ {
var persistMessageProcessor = sp.GetService<IPersistMessageProcessor>(); cancellationToken.ThrowIfCancellationRequested();
return await condition();
Guard.Against.Null(persistMessageProcessor, nameof(persistMessageProcessor)); },
timeoutSecond: (int)Math.Ceiling(effectiveTimeout.TotalSeconds),
var filter = await persistMessageProcessor.GetByFilterAsync(x => pollInterval: effectivePollInterval,
x.DeliveryType == MessageDeliveryType.Internal && typeof(TInternalCommand).ToString() == x.DataType cancellationToken: cancellationToken
); );
var res = filter.Any(x => x.MessageStatus == MessageStatus.Processed);
return res;
});
});
return result;
} }
// Ref: https://tech.energyhelpline.com/in-memory-testing-with-masstransit/ // Ref: https://tech.energyhelpline.com/in-memory-testing-with-masstransit/
private async Task<bool> WaitUntilConditionMet(Func<Task<bool>> conditionToMet, int? timeoutSecond = null) private async Task<bool> WaitUntilConditionMet(
Func<Task<bool>> conditionToMet,
int? timeoutSecond = null,
TimeSpan? pollInterval = null,
CancellationToken cancellationToken = default
)
{ {
var time = timeoutSecond ?? Timeout; var time = timeoutSecond ?? Timeout;
var delay = pollInterval ?? TimeSpan.FromMilliseconds(100);
var startTime = DateTime.Now; var startTime = DateTime.UtcNow;
var timeoutExpired = false; while (DateTime.UtcNow - startTime <= TimeSpan.FromSeconds(time))
var meet = await conditionToMet.Invoke();
while (!meet)
{ {
if (timeoutExpired) cancellationToken.ThrowIfCancellationRequested();
{
return false;
}
await Task.Delay(100); if (await conditionToMet.Invoke())
meet = await conditionToMet.Invoke(); return true;
timeoutExpired = DateTime.Now - startTime > TimeSpan.FromSeconds(time);
await Task.Delay(delay, cancellationToken);
} }
return true; return false;
} }
private async Task StartTestContainerAsync() private async Task StartTestContainerAsync()
@ -288,7 +280,6 @@ public class TestFixture<TEntryPoint> : IAsyncLifetime
MongoDbTestContainer = TestContainers.MongoTestContainer(); MongoDbTestContainer = TestContainers.MongoTestContainer();
EventStoreDbTestContainer = TestContainers.EventStoreTestContainer(); EventStoreDbTestContainer = TestContainers.EventStoreTestContainer();
// Start containers in parallel for speed
await Task.WhenAll( await Task.WhenAll(
MongoDbTestContainer.StartAsync(), MongoDbTestContainer.StartAsync(),
PostgresTestcontainer.StartAsync(), PostgresTestcontainer.StartAsync(),
@ -296,9 +287,7 @@ public class TestFixture<TEntryPoint> : IAsyncLifetime
EventStoreDbTestContainer.StartAsync() EventStoreDbTestContainer.StartAsync()
); );
// Start RabbitMQ last and wait extra time
await RabbitMqTestContainer.StartAsync(); await RabbitMqTestContainer.StartAsync();
await Task.Delay(5000); // Give RabbitMQ extra time to initialize
} }
private async Task StopTestContainerAsync() private async Task StopTestContainerAsync()
@ -321,7 +310,7 @@ public class TestFixture<TEntryPoint> : IAsyncLifetime
new("PostgresOptions:ConnectionString:Identity", PostgresTestcontainer.GetConnectionString()), new("PostgresOptions:ConnectionString:Identity", PostgresTestcontainer.GetConnectionString()),
new("PostgresOptions:ConnectionString:Passenger", PostgresTestcontainer.GetConnectionString()), new("PostgresOptions:ConnectionString:Passenger", PostgresTestcontainer.GetConnectionString()),
new("PersistMessageOptions:ConnectionString", PostgresPersistTestContainer.GetConnectionString()), new("PersistMessageOptions:ConnectionString", PostgresPersistTestContainer.GetConnectionString()),
new("RabbitMqOptions:HostName", RabbitMqTestContainer.Hostname), new("RabbitMqOptions:HostName", "127.0.0.1"),
new("RabbitMqOptions:UserName", TestContainers.RabbitMqContainerConfiguration.UserName), new("RabbitMqOptions:UserName", TestContainers.RabbitMqContainerConfiguration.UserName),
new("RabbitMqOptions:Password", TestContainers.RabbitMqContainerConfiguration.Password), new("RabbitMqOptions:Password", TestContainers.RabbitMqContainerConfiguration.Password),
new( new(

View File

@ -10,10 +10,8 @@ namespace Integration.Test.Identity.Features;
public class RegisterNewUserTests : IdentityIntegrationTestBase public class RegisterNewUserTests : IdentityIntegrationTestBase
{ {
public RegisterNewUserTests( public RegisterNewUserTests(TestWriteFixture<Program, IdentityContext> integrationTestFactory)
TestWriteFixture<Program, IdentityContext> integrationTestFactory) : base(integrationTestFactory) : base(integrationTestFactory) { }
{
}
[Fact] [Fact]
public async Task should_create_new_user_to_db_and_publish_message_to_broker() public async Task should_create_new_user_to_db_and_publish_message_to_broker()

View File

@ -1,4 +1,3 @@
using BuildingBlocks.Contracts.EventBus.Messages;
using BuildingBlocks.TestBase; using BuildingBlocks.TestBase;
using FluentAssertions; using FluentAssertions;
using Integration.Test.Fakes; using Integration.Test.Fakes;
@ -21,8 +20,8 @@ public class CompleteRegisterPassengerTests : PassengerIntegrationTestBase
var userCreated = new FakeUserCreated().Generate(); var userCreated = new FakeUserCreated().Generate();
await Fixture.Publish(userCreated); await Fixture.Publish(userCreated);
(await Fixture.WaitForPublishing<UserCreated>()).Should().Be(true);
(await Fixture.WaitForConsuming<UserCreated>()).Should().Be(true); (await WaitUntilPassengerCreatedAsync(userCreated.PassportNumber)).Should().BeTrue();
var command = new FakeCompleteRegisterPassengerCommand(userCreated.PassportNumber).Generate(); var command = new FakeCompleteRegisterPassengerCommand(userCreated.PassportNumber).Generate();
@ -36,4 +35,14 @@ public class CompleteRegisterPassengerTests : PassengerIntegrationTestBase
response?.PassengerDto?.PassengerType.ToString().Should().Be(command.PassengerType.ToString()); response?.PassengerDto?.PassengerType.ToString().Should().Be(command.PassengerType.ToString());
response?.PassengerDto?.Age.Should().Be(command.Age); response?.PassengerDto?.Age.Should().Be(command.Age);
} }
private Task<bool> WaitUntilPassengerCreatedAsync(string passportNumber)
{
return Fixture.WaitUntilAsync(async () =>
{
return await Fixture.ExecuteDbContextAsync(db =>
ValueTask.FromResult(db.Passengers.Any(p => p.PassportNumber.Value == passportNumber))
);
});
}
} }