How can I avoid passing the scheduler through my business logic, when writing tests for RXJS observables?

我的梦境 提交于 2019-12-23 16:34:06

问题


I am finding that the only way to make certain tests pass is to explicitly pass a scheduler to functions. For illustration, consider this function:

function doStuff( stream ){
    return stream.delay(100)
        .filter( x => x % 2 === 0 )
        .map( x => x * 2 )
        .flatMapLatest( x=> Rx.Observable.range( x, x+100) )

And a test:

it('example test', () => {
    let scheduler = new Rx.TestScheduler()
    let xs = scheduler.createHotObservable(
        onNext(210, 1),
        onNext(220, 2),
        onNext(230, 3)
    )

    let res = scheduler.startScheduler(
        () => doStuff( xs, scheduler ),
        {created:0, subscribed:200, disposed:1000})

    expect( res.messages ).toEqual( [
        onNext(321, 4),
        onNext(322, 5),
        onNext(323, 6)
    ] )
})

Which gives an error:

    Expected [  ] to equal [ ({ time: 321, value: OnNextNotification({ value: 4, kind: 'N' }), comparer: Function }), ({ time: 322, value: OnNextNotification({ value: 5, kind: 'N' }), comparer: Function }), ({ time: 323, value: OnNextNotification({ value: 6, kind: 'N' }), comparer: Function }) ].

This suggests that the delay is happening in real time instead of the simulated time of the TestScheduler.

If I pass the scheduler to every operator, then I can make it work:

function doStuff( stream, scheduler ){
   return stream.delay( 100, scheduler )
      .filter( x => x % 2 === 0 )
      .map( x => x * 2 )
      .flatMapLatest( x => Rx.Observable.range(x, 3, scheduler ) )
}

but it feels to me like I should be able to set the Scheduler once and not have to have my real production code have to thread it through. I was really expecting that, given the original stream is created from the TestScheduler and then run via the same scheduler, that this would all be automatically wired up.


回答1:


The RX guidelines suggest to consider passing a specific scheduler to concurrency introducing operators. For single-threaded Javascript, there is no concurrency, but time-based operators like delay() have a similar concern.

This isn't as bad as I first thought, as the majority of operators do not have a scheduler argument, and only a subset of those are time-based. And this highlights why you would explicitly pass a scheduler. In my example above, I passed through the scheduler to every operator that supported it, but the results weren't exactly as I expected - I even tweaked my "expected" result to make it work:

expect( res.messages ).toEqual( [
    onNext(321, 4),
    onNext(322, 5),
    onNext(323, 6)
] )

But really, I was expecting all of those timings to be 320.

For delay(), I need to inherit the scheduler from the test, but for range() I want the default scheduler instead, because it will schedule the events immediately.

My final example code snippet would then look like this:

function doStuff( stream, scheduler ){
    return stream.delay( 100, scheduler )
        .filter( x => x % 2 === 0 )
        .map( x => x * 2 )
        .flatMapLatest( x => Rx.Observable.range(x, 3))
}

When creating tests, you would always want to be using the TestScheduler for time-based operators. Other operators are likely to want to use the DefaultScheduler regardless of if a test is running.



来源:https://stackoverflow.com/questions/33283214/how-can-i-avoid-passing-the-scheduler-through-my-business-logic-when-writing-te

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!