检查对异步方法的Received()调用

当我运行以下代码时:

[Test] public async Task Can_Test_Update() { var response = await _controller.UpdateAsync(Guid.NewGuid()); response.Valid.Should().BeTrue(); _commands.Received().UpdateAsync( Arg.Is( l => l.Status == Status.Updated)); } 

如果我在“ _commands.Received().UpdateAsync ”之前添加“ await ”,它会抛出一个空引用exception。 我该如何阻止这种情况发生,或await没有必要?

我在这里找到了答案。

 Received.InOrder(async () => { await _Commands.UpdateAsync(Arg.Is(l => l.Status == Status.Updated)); }); 

当NSubstitute看到异步调用时,它会自动创建一个已完成的任务,因此await的工作方式与您在代码中的预期相同(而不是抛出NullReferenceException)。 在这种情况下,这将是您正在测试的方法中从_commands.UpdateAsync(Status.Updated))返回的任务。

另一方面, .Received()调用validation异步方法是否被调用,即完全同步,因此不需要等待它。

要记住的关键是异步方法返回一个Task 。 调用异步方法并返回任务是完全同步的,然后等待Task知道任务所代表的异步操作何时完成。

根据Stack Overflow上的这个答案 ,从NSubstitute版本1.8.3开始,你可以使用await ,它将按预期工作,而不是抛出NullReferenceException。

我刚刚尝试了它,因为我在1.5.0版本并且正如你所描述的那样得到了NullReferenceException,但是现在我处于最新版本(1.10.0),它运行良好。

Jake Ginnivan的答案正确地指出,对于Received await不是必需的,但编译器不理解它并显示

警告CS4014:由于未等待此调用,因此在调用完成之前,将继续执行当前方法。 考虑将’await’运算符应用于调用的结果。

最简单的解决方法是抑制警告

  #pragma warning disable 4014 //for .Received await is not required, so suppress warning “Consider applying the 'await' operator” _publisher.Received(totalNumber).MyMethod(Arg.Any()); #pragma warning restore 4014 

当我在“_commands.Received()。UpdateAsync”之前添加“await”时,它会发生错误空引用

这是因为当你没有await ,方法( Can_Test_Update )可能在它实际检查你传递给方法的空值之前结束,这意味着测试结束。 你有竞争条件。 当您await UpdateAsync ,该方法实际上异步等待操作完成,并且UpdateAsync有机会访问您传递给它的null。

要解决您的错误,只需在UpdateAsync放置一个断点,并查看哪个值作为null传递给该方法。 我怀疑Arg.Is是你的问题。

如果UpdateAsync是一个存根方法,则需要返回一个空Task,而不是null。 您无法等待null任务。

例:

 receivedObject.Stub(s => s.Update(...)).Return(Task.FromResult(0)); 

编辑

问题出在这一行:

 var mockService = Substitute.For(); 

或者更准确地说,当你调用它的方法时:

 await _service.Calculate(); 

您创建了一个模拟服务,但您没有存根该方法。 我不知道如何在Nunit中做到这一点(我们主要使用Rhino,我需要检查),但你需要将你的Calculate方法存根以返回一个空任务(Task.FromResult(0))。 默认情况下,存根方法返回默认返回类型,默认(任务)为空。

关于你的要点 :DoSomethingAsync不应该是异步无效的。 我假设你想要等待它的执行。