次のようなメソッドがあります。
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を完了する前に、私のアサートが呼び出されます。私の通常のコードでは、それがまさに私が望むものです。しかし、単体テストではそれは望ましくありません。
BackgroundWorkerを使用してAsync/Awaitに変換しています。バックグラウンドワーカーでは、BackgroundWorkerの終了を待つことができるため、これは正しく機能していました。
しかし、非同期voidメソッドを待つ方法はないようです...
このメソッドを単体テストするにはどうすればよいですか?
私は単体テストのためにそれを行う方法を見つけました:
[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();
}
ここで重要なのは、ユニットテストを行っているので、非同期呼び出し(非同期void内)を返すタスクに置き換えることができるということです。次に、先に進む前にタスクが完了したことを確認します。
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
を変更します。
_async void
_メソッドは、本質的には「ファイアアンドフォーゲット」メソッドです。完了イベントを取得する方法はありません(外部イベントなしなど)。
これを単体テストする必要がある場合は、代わりに_async Task
_メソッドにすることをお勧めします。その後、結果に対してWait()
を呼び出すことができ、メソッドが完了すると通知されます。
ただし、実際にDoStuff
を直接テストするのではなく、それをラップするDelegateCommand
をテストしているため、このテストメソッドはまだ機能しません。このメソッドを直接テストする必要があります。
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
方法
提供された回答は、非同期メソッドではなくコマンドをテストします。上記のように、非同期メソッドもテストするために別のテストが必要になります。
同様の問題にしばらく時間を費やした後、ユニットテストで非同期メソッドをテストするための簡単な待機が見つかりました。同期的に呼び出すだけです。
protected static void CallSync(Action target)
{
var task = new Task(target);
task.RunSynchronously();
}
および使用法:
CallSync(() => myClass.MyAsyncMethod());
テストはこの行で待機し、結果が準備できた後に続行するため、すぐにアサートできます。
同様の問題がありました。私の場合、解決策は.Returns(...)
のmoqセットアップで_Task.FromResult
_を使用することでした:
_orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(Task.FromResult(null));
_
または、MoqにはReturnsAysnc(...)
メソッドもあります。
タスクを返すようにメソッドを変更すると、Task.Resultを使用できます
bool res = configuration.InitializeAsync(appConfig).Result;
Assert.IsTrue(res);