This isn't a complete example that you can copy paste, but it contains the parts we needed to get it working.
public class UpdateSagaTests
{
private const string TestDatabaseName = "saga-test-db";
private readonly DbContextOptions<DataContext> _options = new DbContextOptionsBuilder<DataContext>()
.UseInMemoryDatabase(TestDatabaseName)
.Options;
private readonly ServiceProvider _serviceProvider;
private readonly Task<InMemoryTestHarness> _harnessSetup;
private readonly DataContext _dataContext;
public UpdateSearchProfileItemsSagaTests()
{
_dataContext = new DataContext(_options);
_serviceProvider = SetupServiceProvider();
_harnessSetup = SetupTestHarnessAsync();
}
[Fact]
public async Task ShouldTestSagaHappyFlow()
{
var harness = await _harnessSetup;
try
{
// Publish the event that triggers the Saga
await harness.Bus.Send<IUpdateCommand>(new UpdateCommand
{
SagaId = sagaId,
Id = id,
Max = 100,
});
var sagaHarness = _serviceProvider.GetRequiredService<ISagaStateMachineTestHarness<UpdateSaga, UpdateSagaState>>();
// Make sure that the event has been consumed and that a saga has been created
Assert.True(await sagaHarness.Consumed.Any<IUpdateCommand>());
Assert.True(await sagaHarness.Created.Any(x => x.CorrelationId == sagaId));
// Validate the state of the saga
var saga = sagaHarness.Created.Contains(sagaId);
Assert.Equal(id, saga.Id);
Assert.Equal(100, saga.Max);
Assert.Equal(4, saga.CurrentState); // 4 == Updating
// The Saga should have produced the following event
Assert.True(await harness.Published.Any<IUpdateSagaCreatedEvent>(context =>
context.Context.Message.SagaId == sagaId &&
context.Context.Message.Id == id)
);
// The Saga should have produced the following command in response.
var sentUpdateDataCommand = await harness.Sent
.SelectAsync<IUpdateDataCommand>(
context => context.Context.Message.Id == id)
.First();
Assert.NotNull(sentUpdateDataCommand);
// Publish an event that will update the saga.
await harness.Bus.Publish<IDataUpdatedEvent>(new DataUpdatedEvent
{
SagaId = sagaId,
...
});
// Make sure that the Saga has processed this event
Assert.True(await sagaHarness.Consumed.Any<IDataUpdatedEvent>(context => context.Context.Message.UpdateInstanceId == sentUpdateDataCommand.Context.Message.UpdateInstanceId));
// And the saga should now have been finalized
Assert.Equal(2, saga.CurrentState); // 2 == Final
}
finally
{
// TODO: For some reason the test will hang on this, until the scheduled timeout triggers
// await harness.Stop();
await _serviceProvider.DisposeAsync();
}
}
private ServiceProvider SetupServiceProvider()
{
// Setup all the dependencies that the Saga has
return new ServiceCollection()
.AddScoped<ILogger<CreateSagaHandler>>(_ => new NullLogger<CreateSagaHandler>())
.AddScoped<CreateSagaHandler>()
...
.AddDbContext<DataContext>(options => options.UseInMemoryDatabase(TestDatabaseName))
.AddMassTransitInMemoryTestHarness(cfg =>
{
cfg.AddMessageScheduler(new Uri("loopback://scheduled/"));
cfg
.AddSagaStateMachine<UpdateSaga, UpdateSagaState>()
.Endpoint(configurator =>
{
configurator.Name = queueName;
})
.InMemoryRepository();
cfg.AddMassTransitTestHarness(configurator =>
{
configurator.AddSagaStateMachine<UpdateSaga, UpdateSagaState>();
});
EndpointConvention.Map<IUpdateDataCommand>(new Uri($"loopback://{nameof(IUpdateDataCommand)}"));
EndpointConvention.Map<IUpdateCommand>(new Uri($"loopback://localhost/{queueName}"));
})
.BuildServiceProvider(true);
}
private async Task<InMemoryTestHarness> SetupTestHarnessAsync()
{
var harness = _serviceProvider.GetRequiredService<InMemoryTestHarness>();
harness.OnConfigureInMemoryBus += configurator =>
{
configurator.UseDelayedMessageScheduler();
configurator.UseInMemoryOutbox();
};
await harness.Start();
return harness;
}
}
}