Ich habe eine Methode, die so aussieht:
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);
}
Ich versuche es so zu testen:
[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();
}
Mein Assert wird aufgerufen, bevor ich mit dem verspotteten LookUpIdAsync fertig bin. In meinem normalen Code möchte ich genau das. Aber für meinen Unit-Test will ich das nicht.
Ich konvertiere zu Async/Await von BackgroundWorker. Mit Background Worker funktionierte dies einwandfrei, da ich auf den BackgroundWorker warten konnte.
Es scheint jedoch nicht möglich zu sein, auf eine asynchrone Void-Methode zu warten ...
Wie kann ich diese Methode testen?
Ich habe einen Weg gefunden, es für Unit-Tests zu tun:
[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();
}
Der Schlüssel hier ist, dass ich, da ich Unit-Tests durchführe, die Aufgabe ersetzen kann, zu der ich meinen asynchronen Anruf (in meinem asynchronen Leerzeichen) zurückgeben möchte. Ich vergewissere mich dann, dass die Aufgabe abgeschlossen ist, bevor ich weitermache.
Sie sollten async void
vermeiden. Verwenden Sie async void
nur für Ereignishandler. DelegateCommand
ist (logisch) ein Event-Handler, also können Sie dies folgendermaßen tun:
// 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);
}
und Gerätetest als:
[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();
}
Meine empfohlene Antwort ist oben. Wenn Sie eine async void
-Methode wirklich testen möchten, können Sie dies mit meiner AsyncEx-Bibliothek tun:
[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();
}
Diese Lösung ändert jedoch die SynchronizationContext
für Ihr Ansichtsmodell während seiner Lebensdauer.
Eine async void
-Methode ist im Wesentlichen eine "Feuer und Vergessen" -Methode. Es gibt keine Möglichkeit, ein Abschlussereignis zurück zu bekommen (ohne ein externes Ereignis usw.).
Wenn Sie einen Gerätetest durchführen müssen, würde ich empfehlen, stattdessen eine async Task
-Methode zu erstellen. Sie können dann Wait()
für die Ergebnisse aufrufen, um Sie darüber zu informieren, wenn die Methode abgeschlossen ist.
Diese Testmethode würde jedoch immer noch nicht funktionieren, da Sie DoStuff
nicht direkt testen, sondern eine DelegateCommand
testen, die sie umschließt. Sie müssen diese Methode direkt testen.
Sie können ein AutoResetEvent verwenden, um die Testmethode anzuhalten, bis der async-Aufruf abgeschlossen ist:
[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);
}
Die angegebene Antwort testet den Befehl und nicht die Async-Methode. Wie oben erwähnt, benötigen Sie einen weiteren Test, um diese asynchrone Methode zu testen.
Nachdem ich einige Zeit mit einem ähnlichen Problem verbracht hatte, fiel es mir leicht, eine asynchrone Methode in einem Komponententest zu testen, indem ich einfach synchron aufrief:
protected static void CallSync(Action target)
{
var task = new Task(target);
task.RunSynchronously();
}
und die Verwendung:
CallSync(() => myClass.MyAsyncMethod());
Der Test wartet auf dieser Zeile und wird fortgesetzt, nachdem das Ergebnis fertig ist, so dass wir sofort danach bestätigen können.
Die einzige Möglichkeit, die ich kenne, ist, Ihre async void
-Methode auf async Task
-Methode umzustellen
Ich hatte ein ähnliches Problem. In meinem Fall bestand die Lösung darin, Task.FromResult
im moq-Setup für .Returns(...)
wie folgt zu verwenden:
orderService.LookUpIdAsync(Arg.Any<long>())
.Returns(Task.FromResult(null));
Alternativ hat Moq auch eine ReturnsAysnc(...)
-Methode.
Ändern Sie Ihre Methode, um eine Task zurückzugeben, und Sie können Task.Result verwenden
bool res = configuration.InitializeAsync(appConfig).Result;
Assert.IsTrue(res);