在 InvokeCommand 中为命令参数编写单元测试
Write unit test for command parameter in InvokeCommand
我想编写一个单元测试来检查我的管道,但没有正确的想法。
作为一个例子,让我们直接从他们的网站上获取管道:
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);
我的想法是在我的单元测试中覆盖 ExecuteSearch
命令并检查提供的参数。但这不起作用,原来的命令被执行了。
这是我的示例测试夹具,我将如何编写测试,但也许我走错了路?
[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);
}
}
}
编辑:
感谢 Krzysztof 在下面的回答,我发现 ExecuteSearch
的 setter 需要提高 INPT,因为 InvokeCommand
的实现寻找 target.WhenAnyValue(commandProperty)
。有了这个改变,它就像一个魅力:)
我将管道更改为:
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
还需要引发 INotifyPropertyChanged.PropertyChanged
事件。
它将使参考保持最新。
我想编写一个单元测试来检查我的管道,但没有正确的想法。 作为一个例子,让我们直接从他们的网站上获取管道:
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);
我的想法是在我的单元测试中覆盖 ExecuteSearch
命令并检查提供的参数。但这不起作用,原来的命令被执行了。
这是我的示例测试夹具,我将如何编写测试,但也许我走错了路?
[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);
}
}
}
编辑:
感谢 Krzysztof 在下面的回答,我发现 ExecuteSearch
的 setter 需要提高 INPT,因为 InvokeCommand
的实现寻找 target.WhenAnyValue(commandProperty)
。有了这个改变,它就像一个魅力:)
我将管道更改为:
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
还需要引发 INotifyPropertyChanged.PropertyChanged
事件。
它将使参考保持最新。