我正在研究单元测试的最佳实践(NUnit)ICommand,特别是MvxCommand
中的MVVMCross实现查看模型
public ICommand GetAuthorisationCommand
{
get { return new MvxCommand(
async () => await GetAuthorisationToken(),
() => !string.IsNullOrWhiteSpace(UserName) && !string.IsNullOrWhiteSpace(Password)); }
}
private async Task GetAuthorisationToken()
{
// ...Do something async
}
单元测试
[Test]
public async Task DoLogonCommandTest()
{
//Arrange
ViewModel vm = new ViewModel(clubCache, authorisationCache, authorisationService);
//Act
await Task.Run(() => vm.GetAuthorisationToken.Execute(null));
//Assert
Assert.Greater(MockDispatcher.Requests.Count, 0);
}
现在我遇到的问题是测试在没有等待异步操作的情况下逐渐消失,从ICommand调用异步方法会感觉有些麻烦。
在单元测试这些ICommands和异步方法时是否有最佳实践?
答案 0 :(得分:7)
您可以使用MvxAsyncCommand
(请参阅:implementation)代替MvxCommand
,并将已发布的GetAuthorisationCommand
类型从ICommand
更改为IMvxAsyncCommand
(但是这个界面不可以通过nuget获得),然后你可以调用
await vm.GetAuthorisationToken.ExecuteAsync();
答案 1 :(得分:3)
我认为the answer with MvxAsyncCommand
是最好的长期解决方案。
但是,如果您想要在不依赖于预发布软件的情况下运行的东西,则可以遵循此模式which I have found helpful when dealing with asynchronous MVVM commands。
首先,定义<!DOCTYPE html>
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-1.12.3.min.js" integrity="sha256-aaODHAgvwQW1bFOGXMeX+pC4PZIPsvn2h1sArYOhgXQ=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<style>
.carousel-control {
padding-top: 10%;
width: 5%;
}
</style>
</head>
<body>
<div class="container">
<div class="col-md-12">
<h1>Bootstrap 3 Thumbnail Slider</h1>
<div class="well">
<div id="myCarousel" class="carousel slide">
<!-- Carousel items -->
<div class="carousel-inner">
<div class="item active">
<div class="row">
<div class="col-sm-3">
<a href="#x"><img src="http://placehold.it/500x500" alt="Image" class="img-responsive"></a>
</div>
<div class="col-sm-3">
<a href="#x"><img src="http://placehold.it/500x500" alt="Image" class="img-responsive"></a>
</div>
<div class="col-sm-3">
<a href="#x"><img src="http://placehold.it/500x500" alt="Image" class="img-responsive"></a>
</div>
<div class="col-sm-3">
<a href="#x"><img src="http://placehold.it/500x500" alt="Image" class="img-responsive"></a>
</div>
</div>
<!--/row-->
</div>
<!--/item-->
<div class="item">
<div class="row">
<div class="col-sm-3">
<a href="#x" class="thumbnail"><img src="http://placehold.it/250x250" alt="Image" class="img-responsive"></a>
</div>
<div class="col-sm-3">
<a href="#x" class="thumbnail"><img src="http://placehold.it/250x250" alt="Image" class="img-responsive"></a>
</div>
<div class="col-sm-3">
<a href="#x" class="thumbnail"><img src="http://placehold.it/250x250" alt="Image" class="img-responsive"></a>
</div>
<div class="col-sm-3">
<a href="#x" class="thumbnail"><img src="http://placehold.it/250x250" alt="Image" class="img-responsive"></a>
</div>
</div>
<!--/row-->
</div>
<!--/item-->
<div class="item">
<div class="row">
<div class="col-sm-3">
<a href="#x" class="thumbnail"><img src="http://placehold.it/250x250" alt="Image" class="img-responsive"></a>
</div>
<div class="col-sm-3">
<a href="#x" class="thumbnail"><img src="http://placehold.it/250x250" alt="Image" class="img-responsive"></a>
</div>
<div class="col-sm-3">
<a href="#x" class="thumbnail"><img src="http://placehold.it/250x250" alt="Image" class="img-responsive"></a>
</div>
<div class="col-sm-3">
<a href="#x" class="thumbnail"><img src="http://placehold.it/250x250" alt="Image" class="img-responsive"></a>
</div>
</div>
<!--/row-->
</div>
<!--/item-->
</div>
<!--/carousel-inner--> <a class="left carousel-control" href="#myCarousel" data-slide="prev"><</a>
<a class="right carousel-control" href="#myCarousel" data-slide="next">></a>
</div>
<!--/myCarousel-->
</div>
<!--/well-->
</div>
</div>
<script language="javascript">
$(document).ready(function () {
$('#myCarousel').carousel({
interval: 10000
})
$('#myCarousel').on('slid.bs.carousel', function () {
//alert("slid");
});
});
</script>
</body>
</html>
:
IAsyncCommand
然后您可以定义interface IAsyncCommand: ICommand
{
Task ExecuteAsync(object parameter);
}
实现:
AsyncCommand
然后在单元测试中使用public class AsyncCommand: MvxCommand, IAsyncCommand
{
private readonly Func<Task> _execute;
public AsyncCommand(Func<Task> execute)
: this(execute, null)
{
}
public AsyncCommand(Func<Task> execute, Func<bool> canExecute)
: base(async () => await execute(), canExecute)
{
_execute = execute;
}
public Task ExecuteAsync()
{
_execute();
}
}
代替await command.ExecuteAsync()
。
答案 2 :(得分:0)
由于命令是火灾和遗忘事件,因此您无法直接完成任务。 我建议将测试分成两个动作(甚至创建两个单元测试)。
有些事情:
//Act
var canExecute = vm.GetAuthorisationToken.CanExecute();
var result = await vm.GetAuthorisationToken();
但是,需要GetAuthorisationToken
将其保护级别从私有中更改,以便为单元测试公开它。
<强>替代地强>
您可以使用AsyncEx等库,这可以让您等待异步调用的完成。
[Test]
public async Task DoLogonCommandTest()
{
AsyncContext.Run(() =>
{
//Arrange
ViewModel vm = new ViewModel(clubCache, authorisationCache, authorisationService);
//Act
await Task.Run(() => vm.GetAuthorisationToken.Execute(null));
});
//Assert
Assert.Greater(MockDispatcher.Requests.Count, 0);
}