我正在尝试在测试中将reactui测试计划程序与异步方法一起使用。
等待异步调用时,测试将挂起。
根本原因似乎是异步方法中等待的命令。
[Fact]
public async Task Test()
=> await new TestScheduler().With(async scheduler =>
{
await SomeAsyncMethod();
// *** execution never gets here
Debugger.Break();
});
private async Task SomeAsyncMethod()
{
var command = ReactiveCommand.CreateFromTask(async () =>
{
await Task.Delay(100);
});
// *** this hangs
await command.Execute();
}
如何与不会死锁的测试计划程序结合使用异步调用?
我正在使用reactui 9.4.1
编辑:
我已经按照Funks答案中的建议尝试过WithAsync()
方法,但是行为是相同的。
答案 0 :(得分:3)
如何与测试计划程序一起进行异步调用?
简而言之
command.Execute()
很冷。您需要订阅它,而不是使用await
。
鉴于您对TestScheduler
感兴趣,我认为您想测试一些涉及时间的事物。但是,从When should I care about scheduling部分开始:
通过“ new Thread()”或“ Task.Run”创建的线程无法在单元测试中进行控制。
因此,例如,如果您要检查Task
是否在100毫秒内完成,则必须等待直到异步方法完成。可以肯定的是,这不是测试TestScheduler
的目的。
稍长的版本
TestScheduler
的目的是通过使事物运转并在某些时间点验证状态来验证工作流程。由于我们只能在TestScheduler
上操作时间,因此,由于无法快速转发实际的计算或I / O,因此通常不希望等待真正的异步代码完成。请记住,这是关于验证工作流的:vm.A
在20毫秒时具有新值,因此vm.B
应该在120毫秒时具有新值,...
那么如何测试SUT?
1 \您可以使用scheduler.CreateColdObservable
public class ViewModelTests
{
[Fact]
public void Test()
{
string observed = "";
new TestScheduler().With(scheduler =>
{
var observable = scheduler.CreateColdObservable(
scheduler.OnNextAt(100, "Done"));
observable.Subscribe(value => observed = value);
Assert.Equal("", observed);
scheduler.AdvanceByMs(99);
Assert.Equal("", observed);
scheduler.AdvanceByMs(1);
Assert.Equal("Done", observed);
});
}
}
在这里,我们基本上将command.Execute()
替换为在var observable
上创建的scheduler
。
很明显,上面的示例非常简单,但是通过多个可观察对象的相互通知,这种测试可以提供有价值的见解以及重构时的安全网。
参考:
2 \您可以明确引用IScheduler
a)使用RxApp
提供的调度程序public class MyViewModel : ReactiveObject
{
public string Observed { get; set; }
public MyViewModel()
{
Observed = "";
this.MyCommand = ReactiveCommand
.CreateFromTask(SomeAsyncMethod);
}
public ReactiveCommand<Unit, Unit> MyCommand { get; }
private async Task SomeAsyncMethod()
{
await RxApp.TaskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));
Observed = "Done";
}
}
public class ViewModelTests
{
[Fact]
public void Test()
{
new TestScheduler().With(scheduler =>
{
var vm = new MyViewModel();
vm.MyCommand.Execute().Subscribe();
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(99);
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(1);
Assert.Equal("Done", vm.Observed);
});
}
}
注意
CreateFromTask
创建具有异步执行逻辑的ReactiveCommand
。无需将Test
方法定义为异步或等待TestScheduler
。
在With
扩展方法的范围内RxApp.TaskpoolScheduler
= RxApp.MainThreadScheduler
= new TestScheduler()
。
b)通过构造函数注入管理自己的调度程序
public class MyViewModel : ReactiveObject
{
private readonly IScheduler _taskpoolScheduler;
public string Observed { get; set; }
public MyViewModel(IScheduler scheduler)
{
_taskpoolScheduler = scheduler;
Observed = "";
this.MyCommand = ReactiveCommand
.CreateFromTask(SomeAsyncMethod);
}
public ReactiveCommand<Unit, Unit> MyCommand { get; }
private async Task SomeAsyncMethod()
{
await _taskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));
Observed = "Done";
}
}
public class ViewModelTests
{
[Fact]
public void Test()
{
new TestScheduler().With(scheduler =>
{
var vm = new MyViewModel(scheduler); ;
vm.MyCommand.Execute().Subscribe();
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(99);
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(0);
Assert.Equal("Done", vm.Observed);
});
}
}
参考:
与Haacked的另一句名言让我们保持密切联系:
不幸的是,这一点很重要,
TestScheduler
并没有延伸到现实生活中,因此您的恶作剧仅限于异步Reactive代码。因此,如果您在测试中调用Thread.Sleep(1000)
,则该线程将真正阻塞一秒钟。但是就测试调度程序而言,还没有时间。
答案 1 :(得分:1)
调用嵌套方法时是否尝试过使用ConfigureAwait(false)?
[Fact]
public async Task Test()
=> await new TestScheduler().With(async scheduler =>
{
// this hangs
await SomeAsyncMethod().ConfigureAwait(false);
// ***** execution will never get to here
Debugger.Break();
}
答案 2 :(得分:0)
请尝试在所有异步方法上使用[Fact]
public async Task Test()
=> await new TestScheduler().With(async scheduler =>
{
await SomeAsyncMethod().ConfigureAwait(false);
// *** execution never gets here
Debugger.Break();
}).ConfigureAwait(false);
private async Task SomeAsyncMethod()
{
var command = ReactiveCommand.CreateFromTask(async () =>
{
await Task.Delay(100).ConfigureAwait(false);
}).ConfigureAwait(false);
// *** this hangs
await command.Execute();
}
。
这将为您提供非阻塞行为。
%252F
测试问题是否与ConfigureAwait相关的另一种方法是将项目移植到Asp.Net Core并在那里进行测试。
Asp.net核心不需要使用ConfigureAwait来防止此阻止问题。