ReactiveUI - 取消另一个命令 - 文档中代码的单元测试失败

ReactiveUI - Cancel a command from another - Unit Test of code from documentation fails

我从 ReactiveUi website documentation 中获取代码并尝试对其进行单元测试,但失败了。
它是关于调用一个命令来取消另一个命令。

下面是要测试的class。

public class SomeViewModel : ReactiveObject
{
    public SomeViewModel(IScheduler scheduler)
    {
        this.CancelableCommand = ReactiveCommand
            .CreateFromObservable(
                () => Observable
                    .StartAsync(DoSomethingAsync)
                    .TakeUntil(CancelCommand), outputScheduler: scheduler);
        this.CancelCommand = ReactiveCommand.Create(
            () =>
            {
                Debug.WriteLine("Cancelling");
            },
            this.CancelableCommand.IsExecuting, scheduler);
    }

    public ReactiveCommand<Unit, Unit> CancelableCommand
    {
        get;
        private set;
    }

    public ReactiveCommand<Unit, Unit> CancelCommand
    {
        get;
        private set;
    }

    public bool IsCancelled { get; private set; }

    private async Task DoSomethingAsync(CancellationToken ct)
    {
        try
        {
            await Task.Delay(TimeSpan.FromSeconds(3), ct);
        }
        catch (TaskCanceledException)
        {
            IsCancelled = true;
        }
    }
}

这是单元测试:

[TestFixture]
[Category("ViewModels")]
public class SomeViewModelFixture
{
    public async Task Executing_cancel_should_cancel_cancelableTask()
    {
        var sut = new SomeViewModel(Scheduler.Immediate);
        var t = sut.CancelableCommand.Execute();
        await sut.CancelCommand.Execute();

        await t;

        Assert.IsTrue(sut.IsCancelled);
    }
}

CancelCommand 被执行(控制台日志上的断点被命中)但是 Task.Delay 永远不会被取消。好像TakeUntil没有要求取消。

更新
我编辑了上面的代码,以便我的 ViewModel ctor 使用 IScheduler 并根据 that issue about unit testing 创建命令,但测试仍然失败。
我尝试了 nUnit 和 xUnit。
我还尝试按照 that article 在我的测试设置中执行 RxApp.MainThreadScheduler = Scheduler.Immediate 但仍然失败。

更新2
从我标记为答案的解决方案和评论来看,最简单的是不在ctor中使用IScheduler然后这样写测试就通过了。

[Test]
    public async Task Executing_cancel_should_cancel_cancelableTask()
    {
        var sut = new SomeViewModel();
        sut.CancelableCommand.Execute().Subscribe();
        await sut.CancelCommand.Execute();

        Assert.IsTrue(sut.IsCancelled);
    }

这对我有用:

public class SomeViewModelTest
{
    SomeViewModel m_actual;

    [SetUp]
    public void Setup()
    {
        m_actual = new SomeViewModel(CurrentThreadScheduler.Instance); 
        m_actual.Activator.Activate();
    }

    [Test]
    public void Executing_cancel_should_cancel_cancelableTask()
    {
        m_actual.CancelableCommand.Execute().Subscribe();
        m_actual.CancelCommand.Execute().Subscribe();

        Assert.IsTrue(m_actual.IsCancelled);
    } 
}

我将调度程序更改为使用与测试本身相同的调度程序,并且我的 ViewModel 确实实现了 ISupportsActivation,我敢说这里不会有任何区别。 除此之外,我从测试中删除了 async/await,你不需要使用 Rx,只需订阅命令即可。

问题是您在取消命令后等待命令的执行。由于取消发生在命令执行之前,因此不会影响命令的执行。您可以通过以下方式让它通过:

public async Task Executing_cancel_should_cancel_cancelableTask()
{
    var sut = new SomeViewModel(Scheduler.Immediate);
    sut.CancelableCommand.Execute().Subscribe();
    await sut.CancelCommand.Execute();

    Assert.True(sut.IsCancelled);
}

我在这里立即开始执行命令(通过订阅)。随后的取消会影响该执行。

总的来说,混合 Rx 和 TPL 总是有点混乱。它有效,但每个角落都潜伏着像这样的陷阱和麻烦。作为 longer-term 解决方案,我强烈建议转向 "pure" Rx。你不会回头看 - 这太棒了。