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

Check calls Received() for async method

当我运行以下代码时:

[Test]
public async Task Can_Test_Update()
{
    var response = await _controller.UpdateAsync(Guid.NewGuid());
    response.Valid.Should().BeTrue();

    _commands.Received().UpdateAsync(
        Arg.Is<Something>(
            l => l.Status == Status.Updated)); 
}

如果我在“_commands.Received().UpdateAsync”之前添加“await”,它会抛出空引用异常。我怎样才能阻止这种情况发生,或者 await 没有必要?

when i add "await" preceding the "_commands.Received().UpdateAsync", it occurs error null reference

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

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

如果 UpdateAsync 是存根方法,您需要 return 一个空任务,而不是 null。您不能等待空任务。

示例:

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

编辑

问题出在这一行:

var mockService = Substitute.For<ICalculationServiceAsync>(); 

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

await _service.Calculate();

您创建了一个模拟服务,但没有对方法进行存根。我不确定如何在 Nunit 中执行此操作(我们主要使用 Rhino,我需要检查一下),但您需要将计算方法存入 return 一个空任务 (Task.FromResult(0 )).默认情况下,存根方法 return 默认 return 类型和默认(任务)为空。

关于 your gist:DoSomethingAsync 不应该是 async void。我假设你想等待它的执行。

我找到了答案 here

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

当 NSubstitute 看到一个异步调用时,它会自动创建一个已完成的任务,以便 await 按照您在代码中预期的方式工作(并且不会抛出 NullReferenceException)。在这种情况下,这将是您正在测试的方法中从 _commands.UpdateAsync(Status.Updated)) 编辑的任务 return。

另一方面,.Received() 调用正在验证是否调用了异步方法,它是完全同步的,因此不需要等待。

要记住的关键是异步方法 return 和 Task。调用异步方法和 return 执行任务是完全同步的,然后等待 Task 知道任务代表的异步操作何时完成。

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

我刚刚试用了 1.5.0 版本并得到了你描述的 NullReferenceException,但现在我使用的是最新版本 (1.10.0),它运行良好。

正确指出 Received 不需要等待,但是编译器不理解它并显示

warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

最简单的解决方法是取消警告

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

显然你可以简单地 await Received 方法:

[Test]
public async Task Can_Test_Update()
{
    var response = await _controller.UpdateAsync(Guid.NewGuid());
    response.Valid.Should().BeTrue();

    await _commands.Received().UpdateAsync(
        Arg.Is<Something>(
            l => l.Status == Status.Updated)); 
}