Await a Async Void method call for unit testing

前端 未结 8 1502
北荒
北荒 2020-12-04 17:25

I have a method that looks like this:

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

             


        
相关标签:
8条回答
  • 2020-12-04 17:46

    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.

    0 讨论(0)
  • 2020-12-04 17:48

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

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

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

    0 讨论(0)
  • 2020-12-04 17:50

    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.

    0 讨论(0)
  • 2020-12-04 17:58

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

    // 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:

    [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.

    0 讨论(0)
  • 2020-12-04 18:00

    The only way I know is to turn your async void method to async Task method

    0 讨论(0)
  • 2020-12-04 18:01

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

    bool res = configuration.InitializeAsync(appConfig).Result;
    Assert.IsTrue(res);
    
    0 讨论(0)
提交回复
热议问题