.net 等待一个用于单元测试的UVC void方法调用

biswetbf  于 2023-10-21  发布在  .NET
关注(0)|答案(9)|浏览(100)

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

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

我试着像这样进行单元测试:

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

我的Assert在我完成模拟的LookUpIdAsync之前被调用。在我的正常代码中,这正是我想要的。但是对于我的单元测试,我不希望这样。
我正在从使用BackgroundWorker转换为Async/Await。对于后台工作程序,这是正确的,因为我可以等待BackgroundWorker完成。
但是似乎没有办法等待一个空的方法.

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

kfgdxczn

kfgdxczn1#

你应该避免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);
}

并对它进行单元测试:

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

我推荐的答案在上面。但是如果你真的想测试一个async void方法,你可以用我的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();
}

但是这个解决方案在视图模型的生命周期中改变了SynchronizationContext

erhoui1w

erhoui1w2#

async void方法本质上是一个“fire and forget”方法。没有办法获取完成事件(没有外部事件等)。
如果你需要对它进行单元测试,我建议将它改为async Task方法。然后,您可以对结果调用Wait(),它将在方法完成时通知您。
然而,这种测试方法仍然不能工作,因为您实际上并不是直接测试DoStuff,而是测试 Package 它的DelegateCommand。您需要直接测试此方法。

qnakjoqk

qnakjoqk3#

我想出了一个单元测试的方法:

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

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

0mkxixxg

0mkxixxg4#

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

m528fe3b

m528fe3b5#

所提供的答案测试命令而不是CNOrc方法。正如上面提到的,你还需要另一个测试来测试这个方法。
在花了一些时间处理类似的问题后,我发现在单元测试中测试一个J2EE方法很容易,只需同步调用:

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

其使用方法:

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

测试在这一行等待,并在结果准备好后继续,因此我们可以在之后立即Assert。

57hvy0tb

57hvy0tb6#

您可以使用AutoResetEvent来暂停测试方法,直到AutoResetc调用完成:

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

omqzjyyz7#

更改方法以返回Task,然后可以使用Task.Result

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

js81xvg68#

我最终采用了一种不同的方法,这是在https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void上提出的
您的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;
}

然后,在我的测试中,我没有调用async void方法,而是调用了async Task方法。

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

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

  //+ Assert

}
3gtaxfhh

3gtaxfhh9#

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

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

另外,Moq也有一个ReturnsAysnc(...)方法。

相关问题