84

私は次のようなメソッドを持っています:

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

モックアップされたLookUpIdAsyncを完了する前に、assertが呼び出されます。私の通常のコードでは、それがまさに私が望むものです。しかし、私のユニットテストではそれは望ましくありません。

BackgroundWorkerを使用してAsync/Awaitに変換しています。バックグラウンドワーカーでは、BackgroundWorkerが終了するのを待つことができたため、これは正しく機能していました。

しかし、非同期voidメソッドを待つ方法はないようです...

このメソッドをユニットテストするにはどうすればよいですか?

4

8 に答える 8

76

避けるべき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ライブラリを使用してテストできます。

[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、ビューモデルの存続期間中にを変更します。

于 2013-01-08T03:05:48.700 に答える
62

async voidメソッドは、本質的に「ファイアアンドフォーゲット」メソッドです。完了イベントを取り戻す手段はありません(外部イベントなどなしで)。

これを単体テストする必要がある場合は、async Task代わりにメソッドにすることをお勧めします。次に、結果を呼び出すことができますWait()。結果は、メソッドが完了すると通知されます。

DoStuffただし、実際に直接テストしているのではなく、DelegateCommandそれをラップするテストを行っているため、記述されているこのテストメソッドはまだ機能しません。このメソッドを直接テストする必要があります。

于 2013-01-07T23:09:21.103 に答える
18

私はユニットテストのためにそれを行う方法を考え出しました:

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

ここで重要なのは、私は単体テストを行っているので、(非同期ボイド内の)非同期呼び出しを返すタスクに置き換えることができるということです。次に、先に進む前に、タスクが完了したことを確認します。

于 2013-01-08T17:06:11.187 に答える
8

私が知っている唯一の方法は、あなたのasync void方法をasync Task方法に変えることです

于 2017-05-18T09:43:40.050 に答える
5

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);
}
于 2013-10-11T07:38:04.223 に答える
5

提供された回答は、非同期メソッドではなくコマンドをテストします。上記のように、その非同期メソッドもテストするには、別のテストが必要になります。

同様の問題にしばらく時間を費やした後、同期的に呼び出すだけで、単体テストで非同期メソッドをテストするのを簡単に待つことができました。

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

と使用法:

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

テストはこの行で待機し、結果の準備ができた後も続行されるため、すぐにアサートできます。

于 2015-04-10T14:29:20.340 に答える
2

メソッドを変更してタスクを返すと、Task.Resultを使用できます

bool res = configuration.InitializeAsync(appConfig).Result;
Assert.IsTrue(res);
于 2019-01-19T14:25:39.607 に答える
0

同様の問題がありました。私の場合、解決策は次のようTask.FromResultにmoqセットアップで使用することでした。.Returns(...)

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

あるいは、MoqにもReturnsAysnc(...)メソッドがあります。

于 2019-08-27T20:26:54.233 に答える