【问题标题】:Check calls Received() for async method检查调用 Received() 的异步方法
【发布时间】:2015-06-23 07:06:05
【问题描述】:

当我运行以下代码时:

[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 没有必要?

【问题讨论】:

    标签: c# asynchronous async-await nsubstitute


    【解决方案1】:

    我找到了答案here

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

    【讨论】:

    • 我认为你不需要 lambda 中的额外 Received()Received.InOrder(async () =&gt; { await _Commands.UpdateAsync(...); })
    • 运行测试时,报错:“Expected to receive these calls in order”
    【解决方案2】:

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

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

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

    【讨论】:

    【解决方案3】:

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

    我刚刚尝试过,因为我在 1.5.0 版本上并得到了你描述的 NullReferenceException,但现在我在最新版本 (1.10.0) 上,它运行良好。

    【讨论】:

      【解决方案4】:

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

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

      最简单的解决方法是禁止警告

       #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
      

      【讨论】:

      • 使用moq 的好理由。无需抑制错误即可处理此问题
      • @Liam,当团队选择 mocking 库时,决定使用哪一个的理由不止一个。
      【解决方案5】:

      显然你可以简单地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)); 
      }
      

      【讨论】:

        【解决方案6】:

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

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

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

        【讨论】:

        • 比 Yuval,我添加“等待 _controller.UpdateAsync(Guid.NewGuid());”在内容中。 Received() 用于检查_commands.UpdateAsync() 是否被执行,_commands.UpdateAsync() 只返回Task。
        【解决方案7】:

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

        例子:

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

        编辑

        问题出在这一行:

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

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

        await _service.Calculate();
        

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

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

        【讨论】:

        • @letitbe:更新了我的答案。 DoSomethingAsync 不应该是 async void 因为你不能再等待它了!
        • 我试图模拟方法 .Returns(Task.FromResult(false));但调用 Received(); 时仍会出现空引用异常。
        • 在 SystemUnderTest.DoSomethingAsync 中尝试在等待之前查看 _service.Calculate 返回的内容。如果它为空,那就是问题所在。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-25
        • 1970-01-01
        • 1970-01-01
        • 2020-12-19
        • 1970-01-01
        相关资源
        最近更新 更多