You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 14:24:57