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。你不会回头看 - 这太棒了。
我从 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。你不会回头看 - 这太棒了。