给定以下类:
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
export class ObjectStateContainer<T> {
private currentStateSubject = new BehaviorSubject<T>(undefined);
private $currentState = this.currentStateSubject.asObservable();
public $isDirty = this.$currentState.pipe(map(t => t !== this.t));
constructor(public t: T) {
this.update(t);
}
undoChanges() {
this.currentStateSubject.next(this.t);
}
update(t: T) {
this.currentStateSubject.next(t);
}
}
我想编写一些测试来验证 $isDirty
包含我在执行各种函数调用后期望的值。具体来说,我想测试创建一个变量,更新它,然后撤消更改并验证每个阶段的 $isDirty
的值。目前,我已经看到了两种测试可观察对象的方法,但我不知道如何用它们中的任何一种进行测试。我希望测试执行以下操作:
- 创建一个新的
ObjectStateContainer
。- 断言
$isDirty
为假。
- 断言
- 调用
ObjectStateContainer
的更新。- 断言
$isDirty
为真。
- 断言
- 对
ObjectStateContainer
调用 undoChanges。- 断言
$isDirty
为假。
- 断言
import { ObjectStateContainer } from './object-state-container';
import { TestScheduler } from 'rxjs/testing';
class TestObject {
}
describe('ObjectStateContainer', () => {
let scheduler: TestScheduler;
beforeEach(() =>
scheduler = new TestScheduler((actual, expected) =>
{
expect(actual).toEqual(expected);
})
);
/*
SAME TEST AS ONE BELOW
This is a non-marble test example.
*/
it('should be constructed with isDirty as false', done => {
const objectStateContainer = new ObjectStateContainer(new TestObject());
objectStateContainer.update(new TestObject());
objectStateContainer.undoChanges();
/*
- If done isn't called then the test method will finish immediately without waiting for the asserts inside the subscribe.
- Using done though, it gets called after the first value in the stream and doesn't wait for the other two values to be emitted.
- Also, since the subscribe is being done here after update and undoChanges, the two previous values will already be gone from the stream. The BehaviorSubject backing the observable will retain the last value emitted to the stream which would be false here.
I can't figure out how to test the whole chain of emitted values.
*/
objectStateContainer
.$isDirty
.subscribe(isDirty => {
expect(isDirty).toBe(false);
expect(isDirty).toBe(true);
expect(isDirty).toBe(false);
done();
});
});
/*
SAME TEST AS ONE ABOVE
This is a 'marble' test example.
*/
it('should be constructed with isDirty as false', () => {
scheduler.run(({ expectObservable }) => {
const objectStateContainer = new ObjectStateContainer(new TestObject());
objectStateContainer.update(new TestObject());
objectStateContainer.undoChanges();
/*
- This will fail with some error message about expected length was 3 but got a length of one. This seemingly is happening because the only value emitted after the 'subscribe' being performed by the framework is the one that gets replayed from the BehaviorSubject which would be the one from undoChanges. The other two have already been discarded.
- Since the subscribe is being done here after update and undoChanges, the two previous values will already be gone from the stream. The BehaviorSubject backing the observable will retain the last value emitted to the stream which would be false here.
I can't figure out how to test the whole chain of emitted values.
*/
const expectedMarble = 'abc';
const expectedIsDirty = { a: false, b: true, c: false };
expectObservable(objectStateContainer.$isDirty).toBe(expectedMarble, expectedIsDirty);
});
});
});
最佳答案
我会选择大理石测试:
scheduler.run(({ expectObservable, cold }) => {
const t1 = new TestObject();
const t2 = new TestObject();
const objectStateContainer = new ObjectStateContainer(t1);
const makeDirty$ = cold('----(b|)', { b: t2 }).pipe(tap(t => objectStateContainer.update(t)));
const undoChange$ = cold('----------(c|)', { c: t1 }).pipe(tap(() => objectStateContainer.undoChanges()));
const expected = ' a---b-----c';
const stateValues = { a: false, b: true, c: false };
const events$ = merge(makeDirty$, undoChange$);
const expectedEvents = ' ----b-----(c|)';
expectObservable(events$).toBe(expectedEvents, { b: t2, c: t1 });
expectObservable(objectStateContainer.isDirty$).toBe(expected, stateValues);
});
expectObservable
所做的是订阅给定的 observable 并将每个值/错误/完成事件转换为通知,每个通知都与时间范围配对它已经到了(Source code)。
这些通知
(值/错误/完成)是操作任务的结果。一个 Action 被安排到一个队列中。他们排队的顺序由虚拟时间指示。
例如,cold('----(b|)')
表示:在 frame 4
发送 value b
并且一个完整的通知。
如果您想了解更多关于这些操作如何以及它们如何排队的信息,您可以查看此 SO answer .
在我们的例子中,我们期望:a---b-----c
,这意味着:
第 0 帧
:a
(假)第 4 帧
:b
(真)第 10 帧
:c
(假)
这些帧数从何而来?
- 一切都从
frame 0
开始,此时类几乎没有初始化 cold('----(b|))
- 将在第 4 帧发出t2
cold('------------(c|)')
- 将在10 帧调用
objectStateContainer.undoChanges()
关于RxJs 测试流中的多个值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61730747/