J'essaie d'utiliser l'ordonnanceur de test reactiveui avec une méthode asynchrone dans un test.
Le test est suspendu lorsque l'appel asynchrone est attendu.
La cause fondamentale semble être une commande attendue dans la méthode asynchrone.
[Fact]
public async Task Test()
=> await new TestScheduler().With(async scheduler =>
{
await SomeAsyncMethod();
// *** execution never gets here
Debugger.Break();
});
private async Task SomeAsyncMethod()
{
var command = ReactiveCommand.CreateFromTask(async () =>
{
await Task.Delay(100);
});
// *** this hangs
await command.Execute();
}
Comment puis-je effectuer un appel asynchrone en combinaison avec le planificateur de test sans blocage?
J'utilise reactiveui 9.4.1
MODIFIER:
J'ai essayé la méthode WithAsync()
comme suggéré dans la réponse Funks, mais le comportement est le même.
Comment puis-je effectuer un appel asynchrone en combinaison avec le planificateur de test?
En bref
command.Execute()
est une observable froide. Vous devez vous y abonner au lieu d'utiliser await
.
Étant donné votre intérêt pour TestScheduler
, je suppose que vous voulez tester quelque chose qui demande du temps. Cependant, à partir de Quand devrais-je me préoccuper de la planification section:
les threads créés via "new Thread ()" ou "Task.Run" ne peuvent pas être contrôlés dans un test unitaire.
Donc, si vous voulez vérifier, par exemple, si votre Task
se termine dans un délai de 100 ms, vous devrez attendre que la méthode asynchrone se termine. Pour être sûr, c'est pas le type de test TestScheduler
est destiné.
La version un peu plus longue
TestScheduler
a pour but de vérifier les flux de travaux en mettant les choses en mouvement et en vérifiant l'état à certains moments. Comme nous ne pouvons manipuler le temps que sur une TestScheduler
, vous préférerez généralement ne pas attendre que le code async réel soit complet, étant donné qu'il est impossible de faire avancer rapidement les calculs ou les E/S réels. N'oubliez pas qu'il s'agit de vérifier les flux de travail: vm.A
a une nouvelle valeur à 20 ms, donc vm.B
devrait avoir un nouveau val à 120 ms, ...
Alors, comment pouvez-vous tester le SUT?
1\Vous pouvez vous moquer de la méthode asynchrone en utilisant scheduler.CreateColdObservable
public class ViewModelTests
{
[Fact]
public void Test()
{
string observed = "";
new TestScheduler().With(scheduler =>
{
var observable = scheduler.CreateColdObservable(
scheduler.OnNextAt(100, "Done"));
observable.Subscribe(value => observed = value);
Assert.Equal("", observed);
scheduler.AdvanceByMs(99);
Assert.Equal("", observed);
scheduler.AdvanceByMs(1);
Assert.Equal("Done", observed);
});
}
}
Ici, nous avons essentiellement remplacé command.Execute()
par var observable
créé le scheduler
.
Il est clair que l'exemple ci-dessus est plutôt simple, mais avec plusieurs observables se notifiant, ce type de test peut fournir des informations précieuses, ainsi qu'un filet de sécurité lors de la refactorisation.
Ref:
2\Vous pouvez référencer explicitement la IScheduler
a) Utilisation des planificateurs fournis par RxApp
public class MyViewModel : ReactiveObject
{
public string Observed { get; set; }
public MyViewModel()
{
Observed = "";
this.MyCommand = ReactiveCommand
.CreateFromTask(SomeAsyncMethod);
}
public ReactiveCommand<Unit, Unit> MyCommand { get; }
private async Task SomeAsyncMethod()
{
await RxApp.TaskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));
Observed = "Done";
}
}
public class ViewModelTests
{
[Fact]
public void Test()
{
new TestScheduler().With(scheduler =>
{
var vm = new MyViewModel();
vm.MyCommand.Execute().Subscribe();
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(99);
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(1);
Assert.Equal("Done", vm.Observed);
});
}
}
Remarque
CreateFromTask
crée une ReactiveCommand
avec une logique d'exécution asynchrone. Il n'est pas nécessaire de définir la méthode Test
comme asynchrone ni d'attendre la TestScheduler
.
Dans l'étendue de la méthode d'extension With
RxApp.TaskpoolScheduler
= RxApp.MainThreadScheduler
= la new TestScheduler()
.
b) Gérer vos propres ordonnanceurs par injection de constructeur
public class MyViewModel : ReactiveObject
{
private readonly IScheduler _taskpoolScheduler;
public string Observed { get; set; }
public MyViewModel(IScheduler scheduler)
{
_taskpoolScheduler = scheduler;
Observed = "";
this.MyCommand = ReactiveCommand
.CreateFromTask(SomeAsyncMethod);
}
public ReactiveCommand<Unit, Unit> MyCommand { get; }
private async Task SomeAsyncMethod()
{
await _taskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));
Observed = "Done";
}
}
public class ViewModelTests
{
[Fact]
public void Test()
{
new TestScheduler().With(scheduler =>
{
var vm = new MyViewModel(scheduler); ;
vm.MyCommand.Execute().Subscribe();
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(99);
Assert.Equal("", vm.Observed);
scheduler.AdvanceByMs(0);
Assert.Equal("Done", vm.Observed);
});
}
}
Ref:
Fermons les rangs avec une autre citation de Haacked:
Malheureusement, et ce point est important, la
TestScheduler
ne s’étendant pas dans la vie réelle, vos manigances sont donc limitées à votre code réactif asynchrone. Ainsi, si vous appelezThread.Sleep(1000)
dans votre test, ce thread sera réellement bloqué pendant une seconde. Mais en ce qui concerne le planificateur de test, aucun temps n’est passé.
Avez-vous essayé d'utiliser ConfigureAwait (false) lors de l'appel d'une méthode imbriquée?
[Fact]
public async Task Test()
=> await new TestScheduler().With(async scheduler =>
{
// this hangs
await SomeAsyncMethod().ConfigureAwait(false);
// ***** execution will never get to here
Debugger.Break();
}
Essayez d’utiliser .ConfigureAwait(false)
sur toutes vos méthodes asynchrones ..__ Cela vous donnera un comportement non bloquant.
[Fact]
public async Task Test()
=> await new TestScheduler().With(async scheduler =>
{
await SomeAsyncMethod().ConfigureAwait(false);
// *** execution never gets here
Debugger.Break();
}).ConfigureAwait(false);
private async Task SomeAsyncMethod()
{
var command = ReactiveCommand.CreateFromTask(async () =>
{
await Task.Delay(100).ConfigureAwait(false);
}).ConfigureAwait(false);
// *** this hangs
await command.Execute();
}
Un autre moyen de vérifier si le problème est lié à ConfigureAwait consiste à porter votre projet sur Asp.Net Core et à le tester.
Asp.net core n’a pas besoin d’utiliser ConfigureAwait pour éviter ce problème de blocage.