如何测试Observable.ajax(redux-observable)?

bin*_*tch 3 rxjs5 redux-observable

最近几天,我一直在使用rxjs和redux-observable,并且一直在努力寻找一种方法来测试Observable.ajax。我有以下史诗,它创建了对https://jsonplaceholder.typicode.com/的请求,

export function testApiEpic (action$) {
  return action$.ofType(REQUEST)
    .switchMap(action =>
      Observable.ajax({ url, method })
        .map(data => successTestApi(data.response))
        .catch(error => failureTestApi(error))
        .takeUntil(action$.ofType(CLEAR))
    )
}
Run Code Online (Sandbox Code Playgroud)

哪里,

export const REQUEST = 'my-app/testApi/REQUEST'
export const SUCCESS = 'my-app/testApi/SUCCESS'
export const FAILURE = 'my-app/testApi/FAILURE'
export const CLEAR = 'my-app/testApi/CLEAR'

export function requestTestApi () {
  return { type: REQUEST }
}
export function successTestApi (response) {
  return { type: SUCCESS, response }
}
export function failureTestApi (error) {
  return { type: FAILURE, error }
}
export function clearTestApi () {
  return { type: CLEAR }
}
Run Code Online (Sandbox Code Playgroud)

该代码在浏览器中运行时可以正常运行,但在使用Jest进行测试时则无法正常运行。

我尝试过

1)根据https://redux-observable.js.org/docs/recipes/WritingTests.html创建测试。store.getActions()仅返回{类型:REQUEST}。

const epicMiddleware = createEpicMiddleware(testApiEpic)
const mockStore = configureMockStore([epicMiddleware])

describe.only('fetchUserEpic', () => {
  let store

  beforeEach(() => {
    store = mockStore()
  })

  afterEach(() => {
    epicMiddleware.replaceEpic(testApiEpic)
  })

  it('returns a response, () => {
    store.dispatch({ type: REQUEST })
    expect(store.getActions()).toEqual([
      { type: REQUEST },
      { type: SUCCESS, response }
    ])
  })
})
Run Code Online (Sandbox Code Playgroud)

2)根据Redux-observable创建测试:史诗级的开玩笑测试失败。它返回

超时-jasmine.DEFAULT_TIMEOUT_INTERVAL指定的超时内未调用异步回调。

  it('returns a response', (done) => {
    const action$ = ActionsObservable.of({ type: REQUEST })
    const store = { getState: () => {} }
    testApiEpic(action$, store)
      .toArray()
      .subscribe(actions => {
        expect(actions).to.deep.equal([
          { type: SUCCESS, response }
        ])
        done()
      })
  })
Run Code Online (Sandbox Code Playgroud)

有人可以指出我测试Observable.ajax的正确方法是什么?

jay*_*lps 6

我将遵循来自StackOverflow的第二个示例。要使其正常工作,您需要进行一些小的调整。无需导入Observable.ajax史诗文件并直接使用该引用,而是需要使用某种形式的依赖项注入。一种方法是在创建它时将其提供给中间件。

import { ajax } from 'rxjs/observable/dom/ajax';

const epicMiddleware = createEpicMiddleware(rootEpic, {
  dependencies: { ajax }
});
Run Code Online (Sandbox Code Playgroud)

我们传递的对象dependencies将作为第三个参数提供给所有史诗

export function testApiEpic (action$, store, { ajax }) {
  return action$.ofType(REQUEST)
    .switchMap(action =>
      ajax({ url, method })
        .map(data => successTestApi(data.response))
        .catch(error => failureTestApi(error))
        .takeUntil(action$.ofType(CLEAR))
    );
}
Run Code Online (Sandbox Code Playgroud)

另外,您不能使用dependencies中间件的选项,而只能使用默认参数:

export function testApiEpic (action$, store, ajax = Observable.ajax) {
  return action$.ofType(REQUEST)
    .switchMap(action =>
      ajax({ url, method })
        .map(data => successTestApi(data.response))
        .catch(error => failureTestApi(error))
        .takeUntil(action$.ofType(CLEAR))
    );
}
Run Code Online (Sandbox Code Playgroud)

无论您选择哪种方式,当我们测试史诗时,我们现在都可以直接调用它并为其提供自己的模拟。以下是成功/错误/取消路径的示例这些未经测试,可能会出现问题,但应为您提供一般思路

it('handles success path', (done) => {
  const action$ = ActionsObservable.of(requestTestApi())
  const store = null; // not used by epic
  const dependencies = {
    ajax: (url, method) => Observable.of({ url, method })
  };

  testApiEpic(action$, store, dependencies)
    .toArray()
    .subscribe(actions => {
      expect(actions).to.deep.equal([
        successTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
      ])

      done();
    });
});

it('handles error path', (done) => {
  const action$ = ActionsObservable.of(requestTestApi())
  const store = null; // not used by epic
  const dependencies = {
    ajax: (url, method) => Observable.throw({ url, method })
  };

  testApiEpic(action$, store, dependencies)
    .toArray()
    .subscribe(actions => {
      expect(actions).to.deep.equal([
        failureTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
      ])

      done();
    });
});

it('supports cancellation', (done) => {
  const action$ = ActionsObservable.of(requestTestApi(), clearTestApi())
  const store = null; // not used by epic
  const dependencies = {
    ajax: (url, method) => Observable.of({ url, method }).delay(100)
  };
  const onNext = chai.spy();

  testApiEpic(action$, store, dependencies)
    .toArray()
    .subscribe({
      next: onNext,
      complete: () => {
        onNext.should.not.have.been.called();        
        done();
      }
    });
});
Run Code Online (Sandbox Code Playgroud)