- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我是 ReactiveUI 的新手,正在按照 here 中的示例进行操作,以及我进行的单元测试。
正如预期的那样,示例代码完美运行,但我的单元测试断言当我的 ReactiveCommand< 的
改变,不改变。IsExecuting
属性时 SpinnerVisibility
属性按预期更改
根据示例,我在我的 View 模型上具有用于微调器可见性和执行搜索的命令的属性:
public Visibility SpinnerVisibility => _spinnerVisibility.Value;
public ReactiveCommand<string, List<FlickrPhoto>> ExecuteSearch { get; protected set; }
在 View 模型构造函数中,我设置了 ExecuteSearch
命令,并且 SpinnerVisibility
设置为在命令执行时更改:
public AppViewModel(IGetPhotos photosProvider)
{
ExecuteSearch = ReactiveCommand.CreateFromTask<string, List<FlickrPhoto>>(photosProvider.FromFlickr);
this.WhenAnyValue(search => search.SearchTerm)
.Throttle(TimeSpan.FromMilliseconds(800), RxApp.MainThreadScheduler)
.Select(searchTerm => searchTerm?.Trim())
.DistinctUntilChanged()
.Where(searchTerm => !string.IsNullOrWhiteSpace(searchTerm))
.InvokeCommand(ExecuteSearch);
_spinnerVisibility = ExecuteSearch.IsExecuting
.Select(state => state ? Visibility.Visible : Visibility.Collapsed)
.ToProperty(this, model => model.SpinnerVisibility, Visibility.Hidden);
}
我最初的尝试是直接调用命令:
[Test]
public void SpinnerVisibility_ShouldChangeWhenCommandIsExecuting()
{
var photosProvider = A.Fake<IGetPhotos>();
var fixture = new AppViewModel(photosProvider);
fixture.ExecuteSearch.Execute().Subscribe(_ =>
{
fixture.SpinnerVisibility.Should().Be(Visibility.Visible);
});
fixture.SpinnerVisibility.Should().Be(Visibility.Collapsed);
}
这确实导致了 state => 状态? Visibility.Visible :正在执行 Visibility.Collapsed
lambda,但随后的断言由于某种原因而失败 SpinnerVisibility
仍然是 Collapsed
。
我的下一次尝试是通过使用 TestScheduler
模拟搜索来间接调用该命令:
[Test]
public void SpinnerVisibility_ShouldChangeWhenCommandIsExecuting()
{
new TestScheduler().With(scheduler =>
{
var photosProvider = A.Fake<IGetPhotos>();
var fixture = new AppViewModel(photosProvider);
A.CallTo(() => photosProvider.FromFlickr(A<string>.Ignored)).ReturnsLazily(
() => new List<FlickrPhoto> { new FlickrPhoto { Description = "a thing", Title = "Thing", Url = "https://thing.com" } });
fixture.SearchTerm = "foo";
scheduler.AdvanceByMs(801); // search is throttled by 800ms
fixture.SpinnerVisibility.Should().Be(Visibility.Visible);
});
}
和以前一样,lambda 执行时,state
为 true
但随后立即重新执行,state
返回 false
,大概是因为被 mock 后,photosProvider.FromFlickr
会立即返回(与正常从 API 检索图像不同),这意味着命令不再执行。
然后我看到了 Paul Bett 对 a similar question 的回应,并在我的模拟中添加了一个 Observable.Interval
:
A.CallTo(() => photosProvider.FromFlickr(A<string>.Ignored)).ReturnsLazily(
() =>
{
Observable.Interval(TimeSpan.FromMilliseconds(500), scheduler);
return new List<FlickrPhoto> {new FlickrPhoto {Description = "a thing", Title = "Thing", Url = "https://thing.com"}};
});
和相应的测试变化:
scheduler.AdvanceByMs(501);
fixture.SpinnerVisibility.Should().Be(Visibility.Collapsed);
这没有效果。
最后,我等待了 Interval
:
A.CallTo(() => photosProvider.FromFlickr(A<string>.Ignored)).ReturnsLazily(async
() =>
{
await Observable.Interval(TimeSpan.FromMilliseconds(500), scheduler);
return new List<FlickrPhoto> {new FlickrPhoto {Description = "a thing", Title = "Thing", Url = "https://thing.com"}};
});
这允许 fixture.SpinnerVisibility.Should().Be(Visibility.Visible)
断言通过,但现在无论我将调度程序推进多远,模拟方法似乎永远不会返回,因此后续断言失败。
这种使用 TestScheduler
的方法是否正确/建议?如果是这样,我错过了什么?如果不是,应如何测试此类行为?
最佳答案
首先,您要尝试在一次测试中测试两个独立的事物。将逻辑分离到更有针对性的测试中将使您在将来进行重构时头疼的事情更少。请考虑以下内容:
SearchTerm_InvokesExecuteSearchAfterThrottle
SpinnerVisibility_VisibleWhenExecuteSearchIsExecuting
现在您有了单元 测试,可以单独验证每个功能。如果一个失败了,你就会确切地知道哪个期望被打破了,因为只有一个。现在,进入实际测试...
根据您的代码,我假设您正在使用 NUnit
、FakeItEasy
和 Microsoft.Reactive.Testing
。测试可观察对象的推荐策略是使用 TestScheduler
并断言可观察对象的最终结果。
下面是我将如何实现它们:
using FakeItEasy;
using Microsoft.Reactive.Testing;
using NUnit.Framework;
using ReactiveUI;
using ReactiveUI.Testing;
using System;
using System.Reactive.Concurrency;
...
public sealed class AppViewModelTest : ReactiveTest
{
[Test]
public void SearchTerm_InvokesExecuteSearchAfterThrottle()
{
new TestScheduler().With(scheduler =>
{
var sut = new AppViewModel(A.Dummy<IGetPhotos>());
scheduler.Schedule(() => sut.SearchTerm = "A");
scheduler.Schedule(TimeSpan.FromTicks(200), () => sut.SearchTerm += "B");
scheduler.Schedule(TimeSpan.FromTicks(300), () => sut.SearchTerm += "C");
scheduler.Schedule(TimeSpan.FromTicks(400), () => sut.SearchTerm += "D");
var results = scheduler.Start(
() => sut.ExecuteSearch.IsExecuting,
0, 100, TimeSpan.FromMilliseconds(800).Ticks + 402);
results.Messages.AssertEqual(
OnNext(100, false),
OnNext(TimeSpan.FromMilliseconds(800).Ticks + 401, true)
);
});
}
[Test]
public void SpinnerVisibility_VisibleWhenExecuteSearchIsExecuting()
{
new TestScheduler().With(scheduler =>
{
var sut = new AppViewModel(A.Dummy<IGetPhotos>());
scheduler.Schedule(TimeSpan.FromTicks(300),
() => sut.ExecuteSearch.Execute().Subscribe());
var results = scheduler.Start(
() => sut.WhenAnyValue(x => x.SpinnerVisibility));
results.Messages.AssertEqual(
OnNext(200, Visibility.Collapsed),
OnNext(301, Visibility.Visible),
OnNext(303, Visibility.Collapsed));
});
}
}
请注意,甚至不需要伪造/模拟 IGetPhotos
,因为您的测试不会根据命令的持续时间验证任何内容。他们只关心何时它执行。
有些事情一开始可能很难全神贯注,例如实际发生滴答声的时间,但一旦掌握了它,它就会非常强大。关于 ReactiveUI 在测试中的使用(例如 IsExecuting
、WhenAnyValue
)可能会有一些争论,但我认为它保持简洁。另外,无论如何,您都在应用程序中使用 ReactiveUI,所以如果这些东西破坏了您的测试,我会认为这是一件好事。
关于c# - 绑定(bind)到 ReactiveCommand IsExecuting 的单元测试 ViewModel 属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49338867/
你能比较一下属性吗 我想禁用文本框“txtName”。有两种方式 使用javascript,txtName.disabled = true 使用 ASP.NET, 哪种方法更好,为什么? 最佳答案 我
Count 属性 返回一个集合或 Dictionary 对象包含的项目数。只读。 object.Count object 可以是“应用于”列表中列出的任何集合或对
CompareMode 属性 设置并返回在 Dictionary 对象中比较字符串关键字的比较模式。 object.CompareMode[ = compare] 参数
Column 属性 只读属性,返回 TextStream 文件中当前字符位置的列号。 object.Column object 通常是 TextStream 对象的名称。
AvailableSpace 属性 返回指定的驱动器或网络共享对于用户的可用空间大小。 object.AvailableSpace object 应为 Drive 
Attributes 属性 设置或返回文件或文件夹的属性。可读写或只读(与属性有关)。 object.Attributes [= newattributes] 参数 object
AtEndOfStream 属性 如果文件指针位于 TextStream 文件末,则返回 True;否则如果不为只读则返回 False。 object.A
AtEndOfLine 属性 TextStream 文件中,如果文件指针指向行末标记,就返回 True;否则如果不是只读则返回 False。 object.AtEn
RootFolder 属性 返回一个 Folder 对象,表示指定驱动器的根文件夹。只读。 object.RootFolder object 应为 Dr
Path 属性 返回指定文件、文件夹或驱动器的路径。 object.Path object 应为 File、Folder 或 Drive 对象的名称。 说明 对于驱动器,路径不包含根目录。
ParentFolder 属性 返回指定文件或文件夹的父文件夹。只读。 object.ParentFolder object 应为 File 或 Folder 对象的名称。 说明 以下代码
Name 属性 设置或返回指定的文件或文件夹的名称。可读写。 object.Name [= newname] 参数 object 必选项。应为 File 或&
Line 属性 只读属性,返回 TextStream 文件中的当前行号。 object.Line object 通常是 TextStream 对象的名称。 说明 文件刚
Key 属性 在 Dictionary 对象中设置 key。 object.Key(key) = newkey 参数 object 必选项。通常是 Dictionary 
Item 属性 设置或返回 Dictionary 对象中指定的 key 对应的 item,或返回集合中基于指定的 key 的&
IsRootFolder 属性 如果指定的文件夹是根文件夹,返回 True;否则返回 False。 object.IsRootFolder object 应为&n
IsReady 属性 如果指定的驱动器就绪,返回 True;否则返回 False。 object.IsReady object 应为 Drive&nbs
FreeSpace 属性 返回指定的驱动器或网络共享对于用户的可用空间大小。只读。 object.FreeSpace object 应为 Drive 对象的名称。
FileSystem 属性 返回指定的驱动器使用的文件系统的类型。 object.FileSystem object 应为 Drive 对象的名称。 说明 可
Files 属性 返回由指定文件夹中所有 File 对象(包括隐藏文件和系统文件)组成的 Files 集合。 object.Files object&n
我是一名优秀的程序员,十分优秀!