简体   繁体   English

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

[英]Await a Async Void method call for unit testing

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.在我完成模拟 LookUpIdAsync 之前调用了我的断言。 In my normal code, that is just what I want.在我的正常代码中,这正是我想要的。 But for my Unit test I don't want that.但是对于我的单元测试,我不希望那样。

I am converting to Async/Await from using BackgroundWorker.我正在从使用 BackgroundWorker 转换为 Async/Await。 With background worker this was functioning correctly because I could wait for the BackgroundWorker to finish.使用后台工作人员,这可以正常工作,因为我可以等待 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?如何对这种方法进行单元测试?

You should avoid async void . 你应该避免async void Only use async void for event handlers. 仅对事件处理程序使用async void DelegateCommand is (logically) an event handler, so you can do it like this: 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 void method, 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 SynchronizationContext for your view model during its lifetime. 但是此解决方案会在视图模型的生命周期内更改视图模型的SynchronizationContext

An async void method is essentially a "fire and forget" method. async void方法本质上是一种“即发即忘”的方法。 There is no means of getting back a completion event (without an external event, etc). 没有办法回到完成事件(没有外部事件等)。

If you need to unit test this, I would recommend making it an async Task method instead. 如果您需要对此进行单元测试,我建议您将其作为async Task方法。 You can then call Wait() on the results, which will notify you when the method completes. 然后,您可以对结果调用Wait() ,这将在方法完成时通知您。

However, this test method as written would still not work, as you're not actually testing DoStuff directly, but rather testing a DelegateCommand which wraps it. 但是,这样编写的测试方法仍然无效,因为您实际上并没有直接测试DoStuff ,而是测试包装它的DelegateCommand You would need to test this method directly. 您需要直接测试此方法。

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. 这里的关键是,因为我是单元测试,我可以替换我希望在异步调用(在我的异步void内)返回的任务。 I then just make sure the task has completed before I move on. 然后我确保在继续之前完成任务。

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);
}

我知道的唯一方法是将async void方法转换为async Task方法

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. 测试在此行等待并在结果准备好后继续,以便我们可以立即断言。

Change your method to return a Task and you can use Task.Result 更改您的方法以返回任务,您可以使用Task.Result

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

I had a similar issue. 我有一个类似的问题。 In my case, the solution was to use Task.FromResult in the moq setup for .Returns(...) like so: 在我的情况下,解决方案是在.Returns(...)的moq设置中使用Task.FromResult ,如下所示:

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

Alternatively, Moq also has a ReturnsAysnc(...) method. 或者,Moq也有一个ReturnsAysnc(...)方法。

I ended up with a different approach, something that was suggested in https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void我最终采用了不同的方法,在https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming中提出了一些建议#避免异步无效

All your async void method does is call this async Task method internally.您的async void方法所做的只是在内部调用此async Task方法。

private async void DoStuff(long idToLookUp)
{
    await DoStuffAsync(idLookUp).ConfigureAwait(false);
} 

internal async Task DoStuffAsync(long idToLookUp) //Note: make sure to expose Internal to your test project
{
    IOrder order = await orderService.LookUpIdAsync(idToLookUp);   

    // Close the search
    IsSearchShowing = false;
} 

And then, instead of calling the async void method in my test, I called the async Task method.然后,我没有在我的测试中调用async void方法,而是调用了async Task方法。

[TestMethod]
public async Task TestDoStuff()
{
  //+ Arrange

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

  //+ Assert

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM