【问题标题】:How to test an inner Observable that will not complete?如何测试无法完成的内部 Observable?
【发布时间】:2017-09-16 02:34:59
【问题描述】:

我正在使用jest 来测试一个redux-observable 史诗,它派生了一个使用Observable.fromEvent 创建的内部可观察对象,并在发出动作之前监听特定的按键。

我正在努力测试内部 Observable 何时没有收到此特定按键,因此不会发出操作。

使用 jest,以下超时:

import { Observable, Subject } from 'rxjs'
import { ActionsObservable } from 'redux-observable'
import keycode from 'keycode'

const closeOnEscKeyEpic = action$ =>
    action$.ofType('LISTEN_FOR_ESC').switchMapTo(
        Observable.fromEvent(document, 'keyup')
            .first(event => keycode(event) === 'esc')
            .mapTo({ type: 'ESC_PRESSED' })
    )

const testEpic = ({ setup, test, expect }) =>
    new Promise(resolve => {
        const input$ = new Subject()
        setup(new ActionsObservable(input$))
            .toArray()
            .subscribe(resolve)
        test(input$)
    }).then(expect)

// This times out
it('no action emitted if esc key is not pressed', () => {
    expect.assertions(1)
    return testEpic({
        setup: input$ => closeOnEscKeyEpic(input$),
        test: input$ => {
            // start listening
            input$.next({ type: 'LISTEN_FOR_ESC' })

            // press the wrong keys
            const event = new KeyboardEvent('keyup', {
                keyCode: keycode('p'),
            })
            const event2 = new KeyboardEvent('keyup', {
                keyCode: keycode('1'),
            })
            global.document.dispatchEvent(event)
            global.document.dispatchEvent(event2)

            // end test
            input$.complete()
        },
        expect: actions => {
            expect(actions).toEqual([])
        },
    })
})

我的期望是调用input$.complete() 会导致testEpic 中的promise 得到解决,但对于这个测试它不会。

我觉得我错过了什么。有谁知道为什么这不起作用?

【问题讨论】:

    标签: rxjs jestjs redux-observable


    【解决方案1】:

    我还是 Rx/RxJS 的新手,所以如果这个答案的术语不正确,我深表歉意。不过,我能够重现您的场景。

    内部 observable (Observable.fromEvent) 阻塞了外部 observable。 ActionsObservable 上的已完成事件直到内部 observable 完成后才会传播。

    用这个测试脚本试试下面的代码 sn-p:

    1. 运行代码 sn-p。
    2. 按非 Escape 键。
      • 不应将任何内容打印到控制台。
    3. 选择“监听逃生!”按钮。
    4. 按非 Escape 键。
      • keyCode 应打印到控制台。
    5. 选择“完成!”按钮。
    6. 按非 Escape 键。
      • keyCode 应打印到控制台。
    7. 按退出键。
      • keyCode 应打印到控制台
      • onNext 回调应将 ESC_PRESSED 操作打印到控制台。
      • onComplete 回调应打印到控制台。

    document.getElementById('complete').onclick = onComplete
    document.getElementById('listenForEsc').onclick = onListenForEsc
    
    const actions = new Rx.Subject()
    
    const epic = action$ =>
      action$.pipe(
        Rx.operators.filter(action => action.type === 'LISTEN_FOR_ESC'),
        Rx.operators.switchMapTo(
          Rx.Observable.fromEvent(document, 'keyup').pipe(
            Rx.operators.tap(event => { console.log('keyup: %s', event.keyCode) }),
            Rx.operators.first(event => event.keyCode === 27), // escape
            Rx.operators.mapTo({ type: 'ESC_PRESSED' }),
          )
        )
      )
    
    epic(actions.asObservable()).subscribe(
      action => { console.log('next: %O', action) },
      error => { console.log('error: %O', error) },
      () => { console.log('complete') },
    )
    
    function onListenForEsc() {
      actions.next({ type: 'LISTEN_FOR_ESC' })
    }
    
    function onComplete() {
      actions.complete()
    }
    <script src="https://unpkg.com/rxjs@5.5.0/bundles/Rx.min.js"></script>
    <button id="complete">Complete!</button>
    <button id="listenForEsc">Listen for Escape!</button>

    switchMapTo 大理石图 nor its textual documentation) 都没有清楚地表明当源 observable 在内部 observable 之前完成时会发生什么。但是,上面的代码 sn-p 准确地演示了您在 Jest 测试中观察到的内容。

    我相信这回答了您的“为什么”问题,但我不确定我是否为您提供了明确的解决方案。一种选择是 hook in a cancellation action 并在内部 observable 上使用 takeUntil。但是,如果它只在您的 Jest 测试中使用过,那可能会让人感到尴尬。

    我可以看到这种史诗/模式在实际应用程序中不会成为问题,因为通常情况下,史诗被创建和订阅一次,而不会被取消订阅。但是,根据具体情况(例如,在单个应用程序中多次创建/销毁存储),我可能会看到这会导致订阅挂起和潜在的内存泄漏。谨记在心!

    【讨论】:

      猜你喜欢
      • 2021-12-08
      • 2018-01-03
      • 1970-01-01
      • 2019-05-24
      • 2017-03-29
      • 2018-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多