C# 如何使用xUnit测试包含wait的异步void DelegateCommand方法?

C# 如何使用xUnit测试包含wait的异步void DelegateCommand方法?,c#,async-await,xunit,delegatecommand,C#,Async Await,Xunit,Delegatecommand,这是我第一次为异步方法编写单元测试。我正在使用xUnit。我搜索了所以没有结果。我发现最好的方法是实现示例中的IAsyncLifetime,但对我来说并不管用。我将感谢任何提示如何解决这个问题 目前我所拥有的。在测试的VM中,我有一个命令: public ICommand testresultcommand{get;private set;} 命令在VM构造函数中初始化,如下所示: testresultcommand=newdelegateCommand(OnTestResultExecuteA

这是我第一次为异步方法编写单元测试。我正在使用xUnit。我搜索了所以没有结果。我发现最好的方法是实现示例中的
IAsyncLifetime
,但对我来说并不管用。我将感谢任何提示如何解决这个问题

目前我所拥有的。在测试的VM中,我有一个命令:

public ICommand testresultcommand{get;private set;}

命令在VM构造函数中初始化,如下所示:

testresultcommand=newdelegateCommand(OnTestResultExecuteAsync)

命令调用方法:

private async void OnTestResultExecuteAsync(object obj)
        {
            TokenSource = new CancellationTokenSource();
            CancellationToken = TokenSource.Token;

            await TestHistoricalResultsAsync();
        }
TestHistoricalResultsAsync方法的签名如下:

专用异步任务TestHistoricalResultsAsync()

现在让我们转到单元测试项目。目前在测试类中,我有一个方法:

[Fact]//testing async void
        public void OnTestResultExecuteAsync_ShouldCreateCancellationTokenSource_True()
        {
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            CancellationToken cancellationToken = tokenSource.Token;
            _viewModel.TestResultsCommand.Execute(null);
            Assert.Equal(cancellationToken.CanBeCanceled, _viewModel.CancellationToken.CanBeCanceled);
            Assert.Equal(cancellationToken.IsCancellationRequested, _viewModel.CancellationToken.IsCancellationRequested);
        }
测试给了我一个例外:

消息:System.NullReferenceException:对象引用未设置为 对象的实例

异常的堆栈跟踪是:

提前感谢您的时间和建议。

其中一个原因是它们很难测试。对于您的问题,大多数开发人员都会执行以下操作之一:

  • 定义并使用
    IAsyncCommand
    接口
  • 将其逻辑
    异步任务
    公开
  • 使用支持异步命令的框架,例如MvvmCross
  • 有关详细信息,请参阅我的

    下面是第二种方法的示例:

    TestResultsCommand = new DelegateCommand(async () => await OnTestResultExecuteAsync());
    
    public async Task OnTestResultExecuteAsync()
    {
      TokenSource = new CancellationTokenSource();
      CancellationToken = TokenSource.Token;
    
      await TestHistoricalResultsAsync();
    }
    
    [Fact]
    public async Task OnTestResultExecuteAsync_ShouldCreateCancellationTokenSource_True()
    {
      CancellationTokenSource tokenSource = new CancellationTokenSource();
      CancellationToken cancellationToken = tokenSource.Token;
      await _viewModel.OnTestResultExecuteAsync();
      Assert.Equal(cancellationToken.CanBeCanceled, _viewModel.CancellationToken.CanBeCanceled);
      Assert.Equal(cancellationToken.IsCancellationRequested, _viewModel.CancellationToken.IsCancellationRequested);
    }
    
    如果您不想仅仅为了单元测试而公开
    异步任务
    方法,那么您可以使用某种
    IAsyncCommand
    ;您自己的(如我的文章中所述)或库中的(如MvvmCross)。以下是使用MvvmCross类型的示例:

    public IMvxAsyncCommand TestResultsCommand { get; private set; }
    
    TestResultsCommand = new MvxAsyncCommand(OnTestResultExecuteAsync);
    
    private async Task OnTestResultExecuteAsync() // back to private
    {
      TokenSource = new CancellationTokenSource();
      CancellationToken = TokenSource.Token;
    
      await TestHistoricalResultsAsync();
    }
    
    [Fact]
    public async Task OnTestResultExecuteAsync_ShouldCreateCancellationTokenSource_True()
    {
      CancellationTokenSource tokenSource = new CancellationTokenSource();
      CancellationToken cancellationToken = tokenSource.Token;
      await _viewModel.TestResultsCommand.ExecuteAsync();
      Assert.Equal(cancellationToken.CanBeCanceled, _viewModel.CancellationToken.CanBeCanceled);
      Assert.Equal(cancellationToken.IsCancellationRequested, _viewModel.CancellationToken.IsCancellationRequested);
    }
    
    如果您更喜欢使用
    imvxancommand
    方法,但不希望使用MvvmCross-dependency,则不难实现。

    其中一个原因是它们很难测试。对于您的问题,大多数开发人员都会执行以下操作之一:

  • 定义并使用
    IAsyncCommand
    接口
  • 将其逻辑
    异步任务
    公开
  • 使用支持异步命令的框架,例如MvvmCross
  • 有关详细信息,请参阅我的

    下面是第二种方法的示例:

    TestResultsCommand = new DelegateCommand(async () => await OnTestResultExecuteAsync());
    
    public async Task OnTestResultExecuteAsync()
    {
      TokenSource = new CancellationTokenSource();
      CancellationToken = TokenSource.Token;
    
      await TestHistoricalResultsAsync();
    }
    
    [Fact]
    public async Task OnTestResultExecuteAsync_ShouldCreateCancellationTokenSource_True()
    {
      CancellationTokenSource tokenSource = new CancellationTokenSource();
      CancellationToken cancellationToken = tokenSource.Token;
      await _viewModel.OnTestResultExecuteAsync();
      Assert.Equal(cancellationToken.CanBeCanceled, _viewModel.CancellationToken.CanBeCanceled);
      Assert.Equal(cancellationToken.IsCancellationRequested, _viewModel.CancellationToken.IsCancellationRequested);
    }
    
    如果您不想仅仅为了单元测试而公开
    异步任务
    方法,那么您可以使用某种
    IAsyncCommand
    ;您自己的(如我的文章中所述)或库中的(如MvvmCross)。以下是使用MvvmCross类型的示例:

    public IMvxAsyncCommand TestResultsCommand { get; private set; }
    
    TestResultsCommand = new MvxAsyncCommand(OnTestResultExecuteAsync);
    
    private async Task OnTestResultExecuteAsync() // back to private
    {
      TokenSource = new CancellationTokenSource();
      CancellationToken = TokenSource.Token;
    
      await TestHistoricalResultsAsync();
    }
    
    [Fact]
    public async Task OnTestResultExecuteAsync_ShouldCreateCancellationTokenSource_True()
    {
      CancellationTokenSource tokenSource = new CancellationTokenSource();
      CancellationToken cancellationToken = tokenSource.Token;
      await _viewModel.TestResultsCommand.ExecuteAsync();
      Assert.Equal(cancellationToken.CanBeCanceled, _viewModel.CancellationToken.CanBeCanceled);
      Assert.Equal(cancellationToken.IsCancellationRequested, _viewModel.CancellationToken.IsCancellationRequested);
    }
    

    如果您喜欢使用
    imvxancommand
    方法,但不希望使用MvvmCross-dependency,则不难做到这一点。

    async void
    应仅用于事件处理程序。此外,代码应该被重构为更可靠的代码,以便您能够正确地测试和维护代码。如果无法更好地查看被测试的类,则无法提供更多建议,因为您似乎正在尝试测试私有成员。@Nkosi但是
    async void
    不应也用于
    DelegateCommand
    ?@Nkosi好吧,在我的测试方法中,我刚刚执行了command async
    wait Task.Run()=>_viewModel.testresultcommand.Execute(null))成功了。基于此的解决方案:
    async void
    应仅用于事件处理程序。此外,代码应该被重构为更可靠的代码,以便您能够正确地测试和维护代码。如果无法更好地查看被测试的类,则无法提供更多建议,因为您似乎正在尝试测试私有成员。@Nkosi但是
    async void
    不应也用于
    DelegateCommand
    ?@Nkosi好吧,在我的测试方法中,我刚刚执行了command async
    wait Task.Run()=>_viewModel.testresultcommand.Execute(null))成功了。基于此的解决方案:好的,谢谢您的反馈。它给了我大局。我必须修改我的
    DelegateCommand
    以使用
    testresultcommand=newdelegateCommand(async()=>await-OnTestResultExecuteAsync())。我以前尝试过这个,但是
    DelegateCommand
    不想接受参数,所以我选择了void。我来换这个。非常感谢。您好,我再次测试了您的方法,包括创建
    AsyncCommand
    ,但使用的是测试方法,而不是
    wait\u viewModel.OnTestResultExecuteAsync()我必须使用
    wait Task.Run(()=>_viewModel.testresultcommand.Execute(null))
    ,否则可能是
    TestHistoricalResultsAsync
    不是异步运行的,仍然会删除相同的异常。
    等待任务。Run
    可能无法修复此问题。听起来你有一个竞赛条件,而
    任务.Run
    使它(几乎)花费了足够长的时间。一个更好的解决方案是修复竞争条件。老实说,
    等待任务。运行
    修复此问题
    TestHistoricalResultsAsync
    仅返回
    任务
    ,并使用
    等待任务。Run
    异步执行,不会引发异常。你可以在这里找到源代码:我不确定你所说的修复竞争条件是什么意思?好的,谢谢你的反馈。它给了我大局。我必须修改我的
    DelegateCommand
    以使用
    testresultcommand=newdelegateCommand(async()=>await-OnTestResultExecuteAsync())。我以前尝试过这个,但是
    DelegateCommand
    不想接受参数,所以我选择了void。我来换这个。非常感谢。您好,我再次测试了您的方法,包括创建
    AsyncCommand
    ,但使用的是测试方法,而不是
    wait\u viewModel.OnTestResultExecuteAsync()我必须使用
    wait Task.Run(()=>_viewModel.testresultcommand.Execute(null))
    ,否则似乎是
    TestHistoricalResultsAsync
    不是异步运行的,并且仍然会删除