MassTransit状态机中CompositeEvent的单元测试问题排查
问题分析与解决方案
核心问题原因
你的Saga中FilesState已经变为3(表示两个子事件都已触发),但状态未切换到FileDeletionComplete,大概率是以下两个关键问题导致:
1. 复合事件的子事件未配置关联规则
FilesT1Deleted和FilesT2Deleted作为发布事件,没有配置与Saga实例的关联逻辑,MassTransit无法将这些发布的消息匹配到对应的Saga实例,导致复合事件的触发逻辑没有被执行。
2. 测试中缺少必要的异步等待逻辑
测试中连续发送/发布消息后立即断言,没有等待Saga完成事件处理和状态转换,异步操作的时序问题导致断言时机过早。
具体修复步骤
步骤1:为子事件添加关联配置
在DeleteAccountStateMachine中,给FilesT1Deleted和FilesT2Deleted事件添加关联规则,确保发布的消息能正确关联到对应的Saga实例:
public class DeleteAccountStateMachine : MassTransitStateMachine<DeleteAccountSaga> { // 定义事件 public Event<InitiateAccountDeletion> InitiateAccountDeletion { get; private set; } public Event<FilesT1Deleted> FilesT1Deleted { get; private set; } public Event<FilesT2Deleted> FilesT2Deleted { get; private set; } public Event FilesDeleted { get; private set; } // 定义状态 public State DeletingFiles { get; private set; } public State FileDeletionComplete { get; private set; } public DeleteAccountStateMachine() { InstanceState(x => x.CurrentState); Event(() => InitiateAccountDeletion, x => { x.CorrelateById(context => context.Message.AccountId); }); // 新增:为子事件配置关联 Event(() => FilesT1Deleted, x => { x.CorrelateById(context => context.Message.AccountId); x.OnMissingInstance(m => m.Ignore()); // 可选,忽略找不到实例的消息 }); Event(() => FilesT2Deleted, x => { x.CorrelateById(context => context.Message.AccountId); x.OnMissingInstance(m => m.Ignore()); }); Initially( When(InitiateAccountDeletion) .Send(context => new DeleteFilesT1(context.Message.AccountId)) .Send(context => new DeleteFilesT2(context.Message.AccountId)) .TransitionTo(DeletingFiles) ); CompositeEvent(() => FilesDeleted, x => x.FilesState, FilesT1Deleted, FilesT2Deleted ); During(DeletingFiles, When(FilesDeleted) .TransitionTo(FileDeletionComplete) ); } }
步骤2:优化测试中的异步等待逻辑
在测试中添加必要的等待,确保每个事件都被Saga消费完成,再执行后续操作和断言:
[Fact] public async Task Test() { await using var serviceProvider = new ServiceCollection() .AddMassTransitTestHarness(x => { x.AddSagaStateMachine<DeleteAccountStateMachine, DeleteAccountSaga>(); }) .BuildServiceProvider(true); var testHarness = serviceProvider.GetTestHarness(); var sagaTestHarness = testHarness.GetSagaStateMachineHarness<DeleteAccountStateMachine, DeleteAccountSaga>(); await testHarness.Start(); var accountId = Guid.NewGuid(); var command = new InitiateAccountDeletion(accountId); // 发送初始化命令,等待Saga实例创建并进入DeletingFiles状态 await testHarness.Bus.Send(command); await sagaTestHarness.WaitForInstance(accountId); await sagaTestHarness.WaitForState(accountId, sm => sm.DeletingFiles); // 发布第一个文件删除完成事件,等待被消费 await testHarness.Bus.Publish(new FilesT1Deleted(accountId)); await testHarness.Consumed.Any<FilesT1Deleted>(); // 发布第二个文件删除完成事件,等待被消费 await testHarness.Bus.Publish(new FilesT2Deleted(accountId)); await testHarness.Consumed.Any<FilesT2Deleted>(); // 等待复合事件触发和状态转换(可根据实际情况调整等待时间) await Task.Delay(100); // 或者使用更可靠的循环等待,避免固定延迟 // var timeout = TimeSpan.FromSeconds(5); // var start = DateTime.UtcNow; // bool isInTargetState = false; // while (DateTime.UtcNow - start < timeout) // { // isInTargetState = await sagaTestHarness.Exists(accountId, x => x.FileDeletionComplete); // if (isInTargetState) break; // await Task.Delay(50); // } // Assert.True(isInTargetState); Assert.True(await sagaTestHarness.Exists(accountId, x => x.FileDeletionComplete)); await testHarness.Stop(); }
步骤3:确认Saga实例的字段类型
确保DeleteAccountSaga中的FilesState字段为int类型(MassTransit使用位掩码存储复合事件状态,必须是整数类型):
public class DeleteAccountSaga : SagaStateMachineInstance { public Guid CorrelationId { get; set; } public string CurrentState { get; set; } public int FilesState { get; set; } // 必须为int类型 public Guid AccountId { get; set; } }
内容的提问来源于stack exchange,提问作者GomalId
相关产品推荐
相关产品推荐

