C# 等待 Async Void 方法调用进行单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14205590/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 11:02:43  来源:igfitidea点击:

Await a Async Void method call for unit testing

c#.netunit-testingasync-await

提问by Vaccano

I have a method that looks like this:

我有一个看起来像这样的方法:

private async void DoStuff(long idToLookUp)
{
    IOrder order = await orderService.LookUpIdAsync(idToLookUp);   

    // Close the search
    IsSearchShowing = false;
}    

//Other stuff in case you want to see it
public DelegateCommand<long> DoLookupCommand{ get; set; }
ViewModel()
{
     DoLookupCommand= new DelegateCommand<long>(DoStuff);
}    

I am trying to unit test it like this:

我正在尝试像这样对其进行单元测试:

[TestMethod]
public void TestDoStuff()
{
    //+ Arrange
    myViewModel.IsSearchShowing = true;

    // container is my Unity container and it setup in the init method.
    container.Resolve<IOrderService>().Returns(orderService);
    orderService = Substitute.For<IOrderService>();
    orderService.LookUpIdAsync(Arg.Any<long>())
                .Returns(new Task<IOrder>(() => null));

    //+ Act
    myViewModel.DoLookupCommand.Execute(0);

    //+ Assert
    myViewModel.IsSearchShowing.Should().BeFalse();
}

My assert is called before I get done with the mocked up LookUpIdAsync. In my normal code, that is just what I want. But for my Unit test I don't want that.

我的断言在我完成模拟的 LookUpIdAsync 之前被调用。在我的正常代码中,这正是我想要的。但是对于我的单元测试,我不想要那样。

I am converting to Async/Await from using BackgroundWorker. With background worker this was functioning correctly because I could wait for the BackgroundWorker to finish.

我正在从使用 BackgroundWorker 转换为 Async/Await。使用后台工作者,这可以正常运行,因为我可以等待 BackgroundWorker 完成。

But there does not seem to be a way to wait for a async void method...

但是似乎没有办法等待 async void 方法......

How can I unit test this method?

如何对这种方法进行单元测试?

采纳答案by Vaccano

I figured out a way to do it for unit testing:

我想出了一种方法来进行单元测试:

[TestMethod]
public void TestDoStuff()
{
    //+ Arrange
    myViewModel.IsSearchShowing = true;

    // container is my Unity container and it setup in the init method.
    container.Resolve<IOrderService>().Returns(orderService);
    orderService = Substitute.For<IOrderService>();

    var lookupTask = Task<IOrder>.Factory.StartNew(() =>
                                  {
                                      return new Order();
                                  });

    orderService.LookUpIdAsync(Arg.Any<long>()).Returns(lookupTask);

    //+ Act
    myViewModel.DoLookupCommand.Execute(0);
    lookupTask.Wait();

    //+ Assert
    myViewModel.IsSearchShowing.Should().BeFalse();
}

The key here is that because I am unit testing I can substitute in the task I want to have my async call (inside my async void) to return. I then just make sure the task has completed before I move on.

这里的关键是,因为我正在进行单元测试,所以我可以替换我想要让我的异步调用(在我的 async void 内)返回的任务。然后,在继续之前,我只需确保任务已完成。

回答by Reed Copsey

An async voidmethod is essentially a "fire and forget" method. There is no means of getting back a completion event (without an external event, etc).

一种async void方法本质上是一种“发射后不管”的方法。没有办法恢复完成事件(没有外部事件等)。

If you need to unit test this, I would recommend making it an async Taskmethod instead. You can then call Wait()on the results, which will notify you when the method completes.

如果您需要对此进行单元测试,我建议您将其设为一种async Task方法。然后您可以调用Wait()结果,该结果会在方法完成时通知您。

However, this test method as written would still not work, as you're not actually testing DoStuffdirectly, but rather testing a DelegateCommandwhich wraps it. You would need to test this method directly.

但是,这种编写的测试方法仍然不起作用,因为您实际上不是DoStuff直接测试,而是测试DelegateCommand包装它的 a 。您需要直接测试此方法。

回答by Stephen Cleary

You should avoid async void. Only use async voidfor event handlers. DelegateCommandis (logically) an event handler, so you can do it like this:

你应该避免async void. 仅async void用于事件处理程序。DelegateCommand是(逻辑上)一个事件处理程序,所以你可以这样做:

// Use [InternalsVisibleTo] to share internal methods with the unit test project.
internal async Task DoLookupCommandImpl(long idToLookUp)
{
  IOrder order = await orderService.LookUpIdAsync(idToLookUp);   

  // Close the search
  IsSearchShowing = false;
}

private async void DoStuff(long idToLookUp)
{
  await DoLookupCommandImpl(idToLookup);
}

and unit test it as:

并将其单元测试为:

[TestMethod]
public async Task TestDoStuff()
{
  //+ Arrange
  myViewModel.IsSearchShowing = true;

  // container is my Unity container and it setup in the init method.
  container.Resolve<IOrderService>().Returns(orderService);
  orderService = Substitute.For<IOrderService>();
  orderService.LookUpIdAsync(Arg.Any<long>())
              .Returns(new Task<IOrder>(() => null));

  //+ Act
  await myViewModel.DoLookupCommandImpl(0);

  //+ Assert
  myViewModel.IsSearchShowing.Should().BeFalse();
}


My recommended answer is above. But if you really want to test an async voidmethod, you can do so with my AsyncEx library:

我推荐的答案在上面。但是如果你真的想测试一个async void方法,你可以用我的AsyncEx 库来做

[TestMethod]
public void TestDoStuff()
{
  AsyncContext.Run(() =>
  {
    //+ Arrange
    myViewModel.IsSearchShowing = true;

    // container is my Unity container and it setup in the init method.
    container.Resolve<IOrderService>().Returns(orderService);
    orderService = Substitute.For<IOrderService>();
    orderService.LookUpIdAsync(Arg.Any<long>())
                .Returns(new Task<IOrder>(() => null));

    //+ Act
    myViewModel.DoLookupCommand.Execute(0);
  });

  //+ Assert
  myViewModel.IsSearchShowing.Should().BeFalse();
}

But this solution changes the SynchronizationContextfor your view model during its lifetime.

但是此解决方案SynchronizationContext在其生命周期内更改了您的视图模型。

回答by BalintN

You can use an AutoResetEvent to halt the test method until the async call completes:

您可以使用 AutoResetEvent 停止测试方法,直到异步调用完成:

[TestMethod()]
public void Async_Test()
{
    TypeToTest target = new TypeToTest();
    AutoResetEvent AsyncCallComplete = new AutoResetEvent(false);
    SuccessResponse SuccessResult = null;
    Exception FailureResult = null;

    target.AsyncMethodToTest(
        (SuccessResponse response) =>
        {
            SuccessResult = response;
            AsyncCallComplete.Set();
        },
        (Exception ex) =>
        {
            FailureResult = ex;
            AsyncCallComplete.Set();
        }
    );

    // Wait until either async results signal completion.
    AsyncCallComplete.WaitOne();
    Assert.AreEqual(null, FailureResult);
}

回答by Velimir

The provided answer tests the command and not the async method. As mentioned above you'll need another test to test that async method as well.

提供的答案测试命令而不是异步方法。如上所述,您还需要另一个测试来测试该异步方法。

After spending some time with a similar problem i found an easy wait to test an async method in a unit test by just calling in synchronously:

在遇到类似问题花了一些时间后,我发现只需同步调用即可轻松等待在单元测试中测试异步方法:

    protected static void CallSync(Action target)
    {
        var task = new Task(target);
        task.RunSynchronously();
    }

and the usage:

和用法:

CallSync(() => myClass.MyAsyncMethod());

The test waits on this line and continues after the result is ready so we can assert immediately afterwards.

测试在这条线上等待并在结果准备好后继续,所以我们可以在之后立即断言。

回答by Alex Planchon

The only way I know is to turn your async voidmethod to async Taskmethod

我知道的唯一方法就是把你的async void方法变成async Task方法

回答by Billy Jake O'Connor

Change your method to return a Task and you can use Task.Result

更改您的方法以返回任务,您可以使用 Task.Result

bool res = configuration.InitializeAsync(appConfig).Result;
Assert.IsTrue(res);

回答by datchung

I had a similar issue. In my case, the solution was to use Task.FromResultin the moq setup for .Returns(...)like so:

我有一个类似的问题。就我而言,解决方案是Task.FromResult在 moq 设置中使用,.Returns(...)如下所示:

orderService.LookUpIdAsync(Arg.Any<long>())
    .Returns(Task.FromResult(null));

Alternatively, Moq also has a ReturnsAysnc(...)method.

或者,Moq 也有一个ReturnsAysnc(...)方法。