使用jasmine-marbles测试Subject时序发射行为的问题咨询
你说得没错,核心问题就是Subject只会把订阅之后发送的值推送给订阅者,而toBeObservable是在执行断言的时候才去订阅你的obs$——这时候你提前调用的两次next早就执行完了,自然捕获不到任何值。
下面给你两种更合适的测试方式,能准确测试Subject的实时时序发射行为:
方法一:用Cold Marble触发Subject的Next操作
把Subject的next操作和测试的时间线绑定起来,确保值是在订阅之后发送的:
it('marble testing subject test correctly', () => { const obs$: Subject<number> = new Subject<number>(); // 用cold marble模拟在测试时间线的不同节点发送值 const trigger$ = cold('xy', { x: 42, y: 24 }); // 当trigger$发射值时,转发给Subject trigger$.subscribe(value => obs$.next(value)); // 此时toBeObservable订阅obs$时,trigger$也会被订阅,值会按时序发送 expect(obs$.asObservable()).toBeObservable(hot('xy', { x: 42, y: 24 })); });
这里的cold('xy', ...)会在测试的时间线中,在frame 0发送42,frame 10发送24。当toBeObservable订阅obs$时,trigger$也会被激活,Subject的next操作就和订阅时序完全同步了,自然能捕获到预期的值。
方法二:用TestScheduler手动控制时序
如果需要更精细的时序控制,可以直接使用RxJS的TestScheduler:
it('marble testing subject with TestScheduler', () => { const scheduler = new TestScheduler((actual, expected) => { expect(actual).toEqual(expected); }); scheduler.run(({ cold, expectObservable }) => { const obs$ = new Subject<number>(); // 定义输入的marble和对应值 const inputMarble = 'xy'; const inputValues = { x: 42, y: 24 }; const source$ = cold(inputMarble, inputValues); // 把source$的值转发给Subject source$.subscribe(value => obs$.next(value)); // 断言Subject的输出和输入marble一致 expectObservable(obs$.asObservable()).toBe(inputMarble, inputValues); }); });
TestScheduler.run会帮你管理整个测试的时间线,所有的Observable操作都会在调度器的控制下执行,完全避免了时序错位的问题。
关于ReplaySubject的补充
你提到用ReplaySubject能运行,但需要把marble改成(xy)——这是因为(xy)表示在同一时间点(frame 0)同步发送两个值,而ReplaySubject的特性是会把订阅前发送的所有值重放给新订阅者,所以当toBeObservable订阅时,它会一次性把42和24都推出去,对应(xy)的marble。但这种方式测试的是ReplaySubject的重放行为,而非原生Subject的实时发射逻辑,如果你的目标是测试Subject本身,还是上面两种方法更贴合需求。
内容的提问来源于stack exchange,提问作者sovemp

