【发布时间】:2021-05-20 21:31:53
【问题描述】:
我有一个带有暴露 observable 的 Angular 服务,我正在尝试使用 rxjs TestScheduler 进行大理石测试。服务上的一个方法控制 observable 发出的值,使用 BehaviorSubject 作为源。这是一个非常简单的例子:
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { BehaviorSubject } from "rxjs";
@Injectable()
export class MyService {
private _isVisibleSubject: BehaviorSubject<boolean> = new BehaviorSubject<
boolean
>(true);
isVisible$: Observable<boolean> = this._isVisibleSubject.asObservable();
constructor() {}
toggleVisibility() {
this._isVisibleSubject.next(!this._isVisibleSubject.value);
}
}
MyService 的简单单元测试。我想测试 2 个条件:
- isVisible$ observable 以 true 开头
- toggleVisibility 被调用两次时,isVisible$ 有 3 个值,true、false 和 back to true
这是测试类
import { TestBed } from '@angular/core/testing';
import { TestScheduler } from 'rxjs/testing';
import { MyService } from './my.service';
describe('MyService ', () => {
let service: MyService ;
const testScheduler: TestScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
beforeEach(() => {
TestBed.configureTestingModule({ providers: [MyService] });
service = TestBed.inject(MyService);
});
it('Should start out visible', () => {
// This one is easy enough, it starts out visible and nothing else happens
testScheduler.run((helpers) => {
const { expectObservable } = helpers;
const values: {[key: string]: boolean} = {'a': true};
const expected = 'a';
expectObservable(service.isVisible$).toBe(expected, values);
});
it('Should toggle visibility back and forth', () => {
testScheduler.run((helpers) => {
const { expectObservable } = helpers;
const values: {[key: string]: boolean} = {'a': true, 'b': false};
const expected = 'aba'; // I've also tried with various frames between, eg '-a-b-a'
service.toggleVisibility();
service.toggleVisibility();
expectObservable(service.isVisible$).toBe(expected, values);
});
});
当我运行它时,第一个测试通过,但第二个测试失败并出现类似Expected $.length = 1 to equal 3. 的错误我已经通过运行如下测试验证了该值实际上发生了变化:
testScheduler.run((helpers) => {
const { expectObservable } = helpers;
const values: {[key: string]: boolean} = {'b': false};
const expected = 'b';
service.toggleVisibility();
expectObservable(service.isVisible$).toBe(expected, values);
});
我假设问题是 service.isVisible$ 在调用 expectObservable() 之前没有被订阅,所以以前的值会丢失。我查看了rxjs documentation on marble testing,但无法弄清楚。
我意识到我可以在测试开始时手动设置对 isVisible$ 可观察对象的订阅,并在我进行更改时验证状态,但这并不像使用大理石测试那样好。
我想做的事可能吗?
【问题讨论】:
-
您的示例代码在拼写上有一些不一致,但我认为这不是问题所在。这很可能是滴答声的问题——您需要等待一个滴答声让 JS 引擎能够对更改消息采取行动,因为 JS 并不是真正的多线程。我不会使用主题的值来切换——我会使用具有即时状态更改的单独字段,切换该字段,然后在
.next调用中将新的切换值作为消息发送。 -
另外,请注意 Angular 有一些预构建的测试类来处理需要滴答的事情:tick,它也是 @angular/core 测试库的一部分。
-
@ps2goat 感谢您对不一致之处的提醒。我急忙直接在SO中写作。我希望避免使用 tick() 和 fakeAsync 东西,因为它总是让我觉得有点脏.. 例如:延迟一些任意时间以希望任务完成,但如果使用大理石测试是不可能的,我'必须走那条路
-
@ps2goat 我认为滴答声和相关内容没有问题,因为不涉及异步操作。
标签: angular unit-testing rxjs rxjs-observables