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

如何对C# ReactiveX.IO开发的类编写正确的单元测试?

问题根源

你遇到的问题本质有两点:

  • 默认情况下Rx.NET的大量操作符会使用异步调度器(比如DefaultScheduler/ThreadPoolScheduler),测试方法主线程执行完毕退出时,Subscribe里的回调还没被调度执行
  • 你当前的测试没有等待result这个Observable完成就直接结束,自然不会触发断言逻辑
修复方案

分为两种实现方式,可根据场景选择:

1. 快速临时修复

适合逻辑简单、不需要模拟时间/异步时序的场景,直接阻塞等待Observable执行完成后再做断言:

[TestMethod]
public void TestMethod1()
{
    string userName = "Moeen";
    string password = "moeen123";
    string instrument = "Bass";

    _loggerMock = new Mock<ILogger>();
    _loggerMock.Setup(x => x.RequestInput(Messages.EnterUsername)).Returns(Observable.Return(userName));
    _loggerMock.Setup(x => x.RequestInput(Messages.EnterPassword)).Returns(Observable.Return(password));
    var options = Enum.GetValues(typeof(Instrument)).Cast<Instrument>().Select(val => val.ToString());
    _loggerMock.Setup(x => x.RequestInput($"{Messages.SelectInstrument} [{string.Join("|", options)}]")).Returns(Observable.Return(instrument));
    _loggerMock.Setup(x => x.ShowMessage(""));

    _backendMock = new UnreliableFakeBackend();

    var result = new SignupFlow(_backendMock, _loggerMock.Object).Run();

    // 阻塞等待Observable返回第一个结果,所有回调会执行完成后再往下走
    var actualOutput = result.FirstAsync().Wait();
    // 这里的断言会正常触发
    Assert.AreEqual(true,false);
}

如果你的Observable可能抛出异常,在外层加try-catch捕获后做异常断言即可。

2. 官方推荐规范测试方案(使用测试调度器)

适合需要模拟延迟、超时、时序等复杂异步场景的测试,完全可控无异步不稳定问题:

第一步:安装依赖

安装NuGet包Microsoft.Reactive.Testing

第二步:改造业务类支持调度器注入

不要在SignupFlow里硬编码调度器,改为支持构造函数注入,方便测试时传入测试调度器:

public class SignupFlow
{
    private readonly IBackend _backend;
    private readonly ILogger _logger;
    private readonly IScheduler _taskScheduler;
    private readonly IScheduler _timeoutScheduler;

    // 生产环境默认构造函数,使用系统默认调度器
    public SignupFlow(IBackend backend, ILogger logger) 
        : this(backend, logger, TaskPoolScheduler.Default, DefaultScheduler.Instance)
    {
    }

    // 测试专用构造函数,注入测试调度器
    public SignupFlow(IBackend backend, ILogger logger, IScheduler taskScheduler, IScheduler timeoutScheduler)
    {
        _backend = backend;
        _logger = logger;
        _taskScheduler = taskScheduler;
        _timeoutScheduler = timeoutScheduler;
    }

    public IObservable<SignupResult> Run()
    {
        // 所有需要调度器的操作都使用注入的实例,比如Delay、ObserveOn、Timer等
        return _logger.RequestInput(Messages.EnterUsername)
            .Delay(TimeSpan.FromSeconds(3), _timeoutScheduler)
            .SelectMany(username => _logger.RequestInput(Messages.EnterPassword))
            .ObserveOn(_taskScheduler);
            // 剩余业务逻辑省略
    }
}

第三步:改造测试代码使用TestScheduler

[TestMethod]
public void TestMethod1()
{
    // 初始化测试调度器,所有时序由它完全控制
    var testScheduler = new TestScheduler();
    string userName = "Moeen";
    string password = "moeen123";
    string instrument = "Bass";

    _loggerMock = new Mock<ILogger>();
    // 所有Observable创建都传入测试调度器
    _loggerMock.Setup(x => x.RequestInput(Messages.EnterUsername)).Returns(Observable.Return(userName, testScheduler));
    _loggerMock.Setup(x => x.RequestInput(Messages.EnterPassword)).Returns(Observable.Return(password, testScheduler));
    var options = Enum.GetValues(typeof(Instrument)).Cast<Instrument>().Select(val => val.ToString());
    _loggerMock.Setup(x => x.RequestInput($"{Messages.SelectInstrument} [{string.Join("|", options)}]")).Returns(Observable.Return(instrument, testScheduler));
    _loggerMock.Setup(x => x.ShowMessage(""));

    _backendMock = new UnreliableFakeBackend();

    // 注入测试调度器
    var result = new SignupFlow(_backendMock, _loggerMock.Object, testScheduler, testScheduler).Run();

    object actualResult = null;
    result.Subscribe(x =>
    {
        actualResult = x;
        Assert.AreEqual(true,false);
    });

    // 推进调度器执行所有已调度任务,相当于瞬间走完所有异步流程
    testScheduler.Start();

    // 也可以在这里做断言,此时所有回调已经执行完成
    Assert.IsNotNull(actualResult);
}

改造完成后Subscribe里的断言会被正常触发,测试会正确失败,替换为你实际的业务断言即可。

内容的提问来源于stack exchange,提问作者virtouso

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 12:54:03