I would like to write an unit test to check my pipeline and don't get the right idea to to so. As an example let's take the pipeline directly from their website:
this.WhenAnyValue(x => x.SearchQuery)
.Throttle(TimeSpan.FromSeconds(0.8), RxApp.TaskpoolScheduler)
.Select(query => query?.Trim())
.DistinctUntilChanged()
.Where(query => !string.IsNullOrWhiteSpace(query))
.ObserveOn(RxApp.MainThreadScheduler)
.InvokeCommand(ExecuteSearch);
I my Idea was to overwrite in my unit test the ExecuteSearch
command and check the provided parameter. But this does not work, the original command gets executed.
Here is my example test fixture, how I would write the test, but perhaps I'm on the wrong track?
[TestFixture]
public class TestInvokeCommand
{
[Test]
public void TestPipeline()
{
new TestScheduler().With(scheduler => {
// implementation of ExampleViewModel provided below
var vm = new ExampleViewModel();
vm.Activator.Activate();
var wasExecuted = false;
// Idea: override 'ExecuteSearch Command' so we have access to
// the command parameter and write our validation code.
vm.ExecuteSearch = ReactiveCommand.Create<string>(query => {
// this is never executed
Assert.That(query, Is.EqualTo("TestQuery"));
wasExecuted = true;
});
vm.SearchQuery = "TestQuery";
scheduler.AdvanceByMs(3000);
Assert.That(wasExecuted, Is.True); // fails here
});
}
// internal class to show how the viewmodel would look like
class ExampleViewModel : ReactiveObject, IActivatableViewModel
{
public ViewModelActivator Activator { get; } = new();
public ExampleViewModel()
{
this.WhenActivated(disposables => {
ExecuteSearch = ReactiveCommand.CreateFromTask<string>(async (queryString, token)
=> await Task.Delay(100, token))
.DisposeWith(disposables);
// directly taken from https://www.reactiveui.net/
this.WhenAnyValue(x => x.SearchQuery)
.Throttle(TimeSpan.FromSeconds(0.8), RxApp.TaskpoolScheduler)
.Select(query => query?.Trim())
.DistinctUntilChanged()
.Where(query => !string.IsNullOrWhiteSpace(query))
.ObserveOn(RxApp.MainThreadScheduler)
.InvokeCommand(ExecuteSearch)
.DisposeWith(disposables);
});
}
public ReactiveCommandBase<string, System.Reactive.Unit> ExecuteSearch { get; set; }
private string _searchQuery;
public string SearchQuery
{
get => _searchQuery;
set => this.RaiseAndSetIfChanged(ref _searchQuery, value);
}
}
}
Edit:
Thanks to Krzysztof's answer below I found out that the setter of ExecuteSearch
needs to raise INPT because the implementation of InvokeCommand
looks for target.WhenAnyValue(commandProperty)
. With this change, it works like a charm :)
I changed the pipeline to:
this.WhenAnyValue(x => x.SearchQuery)
.Throttle(TimeSpan.FromSeconds(0.8), RxApp.TaskpoolScheduler)
.Select(query => query?.Trim())
.DistinctUntilChanged()
.Where(query => !string.IsNullOrWhiteSpace(query))
.ObserveOn(RxApp.MainThreadScheduler)
.InvokeCommand(this, x => x.ExecuteSearch);
ExecuteSearch
also needs to raise INotifyPropertyChanged.PropertyChanged
event.
It will keep the reference up to date.