RxJs 测试流中的多个值
RxJs test for multiple values from the stream
给出以下 class:
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
所做的是订阅给定的可观察对象并将每个 value/error/complete 事件转换为通知,每个通知都与 时间范围 配对它到达的位置(Source code).
这些notifications
(value/error/complete)是动作任务的结果。一个动作被安排到一个队列中。它们排队的顺序由 虚拟时间 指示。
例如,cold('----(b|)')
表示:在 frame 4
发送 value b
和一个完整的通知。
如果您想了解更多有关这些操作的方式以及它们如何排队的信息,可以查看此 .
在我们的例子中,我们期望:a---b-----c
,这意味着:
frame 0
: a
(假)
frame 4
: b
(真)
frame 10
: c
(假)
这些帧数是从哪里来的?
- 一切都从
frame 0
开始,目前 class 几乎没有初始化
cold('----(b|))
- 将在第 4 帧发出 t2
cold('----------(c|)')
- 将在帧 10
调用 objectStateContainer.undoChanges()
给出以下 class:
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
所做的是订阅给定的可观察对象并将每个 value/error/complete 事件转换为通知,每个通知都与 时间范围 配对它到达的位置(Source code).
这些notifications
(value/error/complete)是动作任务的结果。一个动作被安排到一个队列中。它们排队的顺序由 虚拟时间 指示。
例如,cold('----(b|)')
表示:在 frame 4
发送 value b
和一个完整的通知。
如果您想了解更多有关这些操作的方式以及它们如何排队的信息,可以查看此
在我们的例子中,我们期望:a---b-----c
,这意味着:
frame 0
:a
(假)frame 4
:b
(真)frame 10
:c
(假)
这些帧数是从哪里来的?
- 一切都从
frame 0
开始,目前 class 几乎没有初始化 cold('----(b|))
- 将在第 4 帧发出 cold('----------(c|)')
- 将在帧10
调用
t2
objectStateContainer.undoChanges()