2016-04-12 7 views
6

wartet ich eine komplizierte Situation für Unit-Tests zu verspotten bin versucht:Async Rückruf bei verspottet Objekt nicht

_mockController = new Mock<IController>(); 
_mockController.Setup(tc => tc.Interrupt(It.IsAny<Func<Task>>())) 
    .Callback<Func<Task>>(async f => await f.Invoke()); 

Wo IController einen Hohlraum Methode Interrupt(Func<Task>> f), das einig Arbeit Warteschlangen zu tun.

Meine Objekte im Test nennen Interrupt(), und ich kann den Anruf wie so überprüfen:

_mockController.Verify(tc => tc.Interrupt(It.IsAny<Func<Task>>()), Times.Once); 

... aber wenn das Argument Func<Task> in der Callback aufgerufen wird, wird das await Schlüsselwort nicht in dem angesehenen Task: Die Ausführung des Tests wird fortgesetzt, bevor die Task beendet wird (trotz await im Rückruf). Ein Symptom dafür ist, dass das Hinzufügen eines await Task.Delay(1000) im Interrupt()Task Argument einen bestandenen Test zu einem fehlgeschlagenen Test macht.

Liegt dieses Verhalten an einer Nuance davon, wie Threads oder Task s während des Tests behandelt werden? Oder eine Einschränkung von Moq? Meine Testmethoden haben diese Unterschrift:

[Test] 
public async Task Test_Name() 
{ 
} 
+0

Ich vermute, es ist eine Beschränkung von Moq, aber Sie müssen ein Problem mit ihnen melden, um sicherzustellen, dass. –

+0

Ich öffnete [diese Ausgabe] (https://github.com/moq/moq4/issues/256) mit Moq. Ich werde mit ihrer Antwort aktualisieren. – jdslepp

+0

Meine Lösung besteht darin, eine Stubbed-Implementierung von IController zu schreiben, die im Grunde eine benutzerdefinierte Mock ist: Sie protokolliert Aufrufe und führt diese Interrupts asynchron in einer Methode aus, die 'Task' zurückgibt, was der Schlüssel ist. – jdslepp

Antwort

6

Callback kann keinen Wert zurück, und somit nicht verwendet werden sollte asynchronen Code (oder Synchroncode, der einen Wert zurückgeben muss) auszuführen. Callback ist eine Art von "Injektionspunkt", die Sie in die Parameter übergeben bis die Methode einhaken können, aber nicht ändern, was es zurückgibt.

Wenn Sie eine Lambda-Mock möchten, können Sie einfach Returns verwenden:

_mockController.Setup(tc => tc.Interrupt(It.IsAny<Func<Task>>())) 
    .Returns(async f => await f()); 

(Ich gehe davon aus Interrupt kehrt Task).

Die Ausführung des Tests wird fortgesetzt, bevor die Task beendet wird (trotz der Wartezeit im Callback).

Ja, da Callback keinen Wert zurückgeben kann, ist es als Action/Action<...> immer typisiert ist, so dass Ihr async Lambda ein async void Verfahren endet als mit all the problems that brings (wie in meinem MSDN-Artikel beschrieben).

Update:

Interrupt() ist eigentlich ein void-Methode: was es tut, ist die Funktion Warteschlange (das Argument), bis die IRegler gestört werden kann, zu stoppen, was es tut. Dann ruft er die Funktion - die eine Aufgabe gibt - und erwartet, dass Task-

Sie dann dieses Verhalten verspotten kann, wenn das funktionieren würde:

List<Func<Task>> queue = new List<Func<Task>>(); 
_mockController.Setup(tc => tc.Interrupt(It.IsAny<Func<Task>>())) 
    .Callback<Func<Task>>(f => queue.Add(f)); 

... // Code that calls Interrupt 

// Start all queued tasks and wait for them to complete. 
await Task.WhenAll(queue.Select(f => f())); 

... // Assert/Verify 
+0

'Interrupt()' ist eigentlich eine void-Methode: was es tut, ist die Funktion der Warteschlange (das Argument), bis die 'IController' gestört werden kann, um zu stoppen, was es tut. Dann ruft es die Funktion auf - die eine 'Aufgabe' zurückgibt - und' wartet 'diese' Aufgabe'. Ich sehe, was Sie damit meinen, hinter den Kulissen "async void" zu werden, und werde mehr darüber nachdenken. – jdslepp

+0

@jdslepp: Meine Antwort wurde mit Pseudo-Code aktualisiert, der diesen Semantiken besser entspricht. –

Verwandte Themen