用于测试调用异步方法的 ICommand 的模式
Patterns for testing ICommand that call async methods
我只是在查看单元测试 (NUnit) 的最佳实践 ICommand and specifically the MvxCommand implementation within 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 调用异步方法时感觉有点笨拙。
在单元测试这类 ICommand 和异步方法时是否有任何最佳实践?
您可以使用 MvxAsyncCommand
(参见:implementation)代替 MvxCommand
,并将 GetAuthorisationCommand
的发布类型从 ICommand
更改为 IMvxAsyncCommand
(但是该接口 还不能通过 nuget 使用)然后你可以调用
await vm.GetAuthorisationToken.ExecuteAsync();
由于命令是一劳永逸的事件,您不会直接返回完成。
我建议将测试分成两个动作(或者甚至创建两个单元测试)。
- 测试命令是否可以执行
- 测试异步任务是否return预期结果
大致如下:
//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);
}
我认为是最好的长期解决方案。
但是,如果您想要在不依赖预发行软件的情况下立即运行的功能,您可以遵循此模式 which I have found helpful when dealing with asynchronous MVVM commands。
首先定义一个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()
而不是 command.Execute()
。
我只是在查看单元测试 (NUnit) 的最佳实践 ICommand and specifically the MvxCommand implementation within 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 调用异步方法时感觉有点笨拙。
在单元测试这类 ICommand 和异步方法时是否有任何最佳实践?
您可以使用 MvxAsyncCommand
(参见:implementation)代替 MvxCommand
,并将 GetAuthorisationCommand
的发布类型从 ICommand
更改为 IMvxAsyncCommand
(但是该接口 还不能通过 nuget 使用)然后你可以调用
await vm.GetAuthorisationToken.ExecuteAsync();
由于命令是一劳永逸的事件,您不会直接返回完成。 我建议将测试分成两个动作(或者甚至创建两个单元测试)。
- 测试命令是否可以执行
- 测试异步任务是否return预期结果
大致如下:
//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);
}
我认为
但是,如果您想要在不依赖预发行软件的情况下立即运行的功能,您可以遵循此模式 which I have found helpful when dealing with asynchronous MVVM commands。
首先定义一个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()
而不是 command.Execute()
。