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

时间:2017-09-26 08:04:10

标签: 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))
    )
}

其中,

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 }
}

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

我试过了,

1)根据https://redux-observable.js.org/docs/recipes/WritingTests.html创建测试。 store.getActions()仅返回{type: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 }
    ])
  })
})

2)根据Redux-observable: failed jest test for epic创建测试。它返回

  

超时 - 在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()
      })
  })

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

2 个答案:

答案 0 :(得分:6)

我会从StackOverflow中跟随第二个例子。为了使它工作,你需要做一些小的调整。您需要使用某种形式的依赖注入,而不是在史诗文件中导入Observable.ajax并直接使用该引用。一种方法是在创建中间件时将其提供给中间件。

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

const epicMiddleware = createEpicMiddleware(rootEpic, {
  dependencies: { ajax }
});

我们作为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))
    );
}

或者,您无法使用中间件的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))
    );
}

你选择哪一个,当我们测试史诗时,我们现在可以直接调用它并为它提供我们自己的模拟。以下是成功/错误/取消路径的示例这些未经测试且可能存在问题,但应该为您提供一般性的想法

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();
      }
    });
});

答案 1 :(得分:0)

第一种方式:

首先,使用isomorphic-fetch而不是Observable.ajax来支持nock,就像这样

const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
  .then(res => res.json());
  return Observable.from(request);
};

所以我的史诗是:

const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
  action$
    .ofType(FETCH_MODEL)
    .mergeMap((action: FetchModel) =>
      fetchDynamicData(action.url, action.params)
        .map((payload: FetchedData) => fetchModelSucc(payload.data))
        .catch(error => Observable.of(
          fetchModelFail(error)
      )));

然后,您可能需要一个间隔来决定何时完成测试。

describe("epics", () => {
  let store: MockStore<{}>;
  beforeEach(() => {
    store = mockStore();
  });
  afterEach(() => {
    nock.cleanAll();
    epicMiddleware.replaceEpic(epic);
  });
  it("fetch data model succ", () => {
    const payload = {
      code: 0,
      data: someData,
      header: {},
      msg: "ok"
    };
    const params = {
      data1: 100,
      data2: "4"
    };
    const mock = nock("https://test.com")
      .get("/test")
      .query(params)
      .reply(200, payload);
    const go = new Promise((resolve) => {
      store.dispatch({
        type: FETCH_MODEL,
        url: "https://test.com/test",
        params
      });
      let interval: number;
      interval = window.setInterval(() => {
        if (mock.isDone()) {
          clearInterval(interval);
          resolve(store.getActions());
        }
      }, 20);
    });
    return expect(go).resolves.toEqual([
      {
        type: FETCH_MODEL,
        url: "https://test.com/assignment",
        params
      },
      {
        type: FETCH_MODEL_SUCC,
        data: somData
      }
    ]);
  });
});

享受它:)