如何在 redux-saga 中测试 api 调用

pun*_*ira 4 testing reactjs jestjs redux-saga react-redux

我有这个传奇效果,它调用 API 并在成功时分派一个操作:

export function* getThemEffect() {
  try {
    yield put(requestActoin());
    const data: AxiosResponse<ServerResponseSchema> = yield call(getStuff);
    yield put(successAction(data.data.data));
  } catch (err: any) {
    yield put(failureAction(err?.response?.data || null));
  }
}
Run Code Online (Sandbox Code Playgroud)

这是辅助函数:

export function getStuff() {
  const config: AxiosRequestConfig = {
    method: "GET",
    url: "https://somewhere.com/api/get"
  };
  return axios(config);
}
Run Code Online (Sandbox Code Playgroud)

这个传奇的测试服看起来像这样:

import * as api from "../api";

const getStuffSpy = jest.spyOn(api, "getStuff");

describe("search saga", () => {
   let gen: Generator, response: any, getStuffMock: jest.Mock;
   beforeEach(() => {
      getStuffSpy.mockClear();
      gen = getThemEffect();
      getStuffMock = jest.fn();
      getStuffSpy.mockImplementation(getStuffMock);
    });
   describe("server success response", () => {
      beforeEach(() => {
        response = { data: { data: ["1", "2", "3"] } };
      });
      it("should create correct success flow", () => {
        expect(gen.next()).toEqual({
          value: put(requestAction()),
          done: false
        });
        expect(gen.next()).toEqual({
          value: call(api.getStuff),
          done: false
        });
        expect(getStuffMock).toHaveBeenCalled(); // <=== this fails
        expect(gen.next(response)).toEqual({
          value: put(successAction(["1", "2", "3"])),
          done: false
        });
        expect(gen.next()).toEqual({
          value: undefined,
          done: true
        });
      });
    });
}
Run Code Online (Sandbox Code Playgroud)

getStuffMock然而,期望函数被调用的测试失败了。我怎样才能解决这个问题?我正在使用 jest 和测试库

sli*_*wp2 8

call(fn, ...args)只是一个返回普通效果对象的函数。它不会fn立即执行调用。当逐步测试 saga 生成器函数时,您手动执行生成器并提供yieldby.next ()方法的值,该getStuff函数将不会执行。

\n

只是call(getStuff)返回一个Effect对象,如下所示:

\n
{\n  CALL: {\n    fn: getStuff,\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如果你想执行模拟getStuff函数,你需要以这种方式测试 saga -测试完整的 Saga

\n

runSaga将获取Effect对象并执行它所拥有的函数。

\n

测试示例:

\n

saga.ts

\n
import { call, put } from \'redux-saga/effects\';\nimport { getStuff } from \'./api\';\n\nexport const requestAction = () => ({ type: \'REQUEST\' });\nexport const successAction = (data) => ({ type: \'SUCCESS\', payload: data });\nexport const failureAction = (error) => ({ type: \'FAILURE\', payload: error, error: true });\n\nexport function* getThemEffect() {\n  try {\n    yield put(requestAction());\n    const data = yield call(getStuff);\n    yield put(successAction(data.data.data));\n  } catch (err: any) {\n    yield put(failureAction(err?.response?.data || null));\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

api.ts

\n
import axios, { AxiosRequestConfig } from \'axios\';\n\nexport function getStuff() {\n  const config: AxiosRequestConfig = {\n    method: \'GET\',\n    url: \'https://somewhere.com/api/get\',\n  };\n  return axios(config);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

saga.test.ts

\n
import { runSaga } from \'@redux-saga/core\';\nimport { call, put } from \'@redux-saga/core/effects\';\nimport { mocked } from \'ts-jest/utils\';\nimport { getStuff } from \'./api\';\nimport { getThemEffect, requestAction, successAction } from \'./saga\';\n\njest.mock(\'./api\');\n\nconst getStuffMock = mocked(getStuff);\n\ndescribe(\'search saga\', () => {\n  it(\'should create correct success flow\', () => {\n    const gen = getThemEffect();\n    const response = { data: { data: [\'1\', \'2\', \'3\'] } };\n    expect(gen.next()).toEqual({\n      value: put(requestAction()),\n      done: false,\n    });\n    expect(gen.next()).toEqual({\n      value: call(getStuff),\n      done: false,\n    });\n\n    expect(gen.next(response)).toEqual({\n      value: put(successAction([\'1\', \'2\', \'3\'])),\n      done: false,\n    });\n    expect(gen.next()).toEqual({\n      value: undefined,\n      done: true,\n    });\n  });\n\n  it(\'should pass\', async () => {\n    const response = { data: { data: [\'1\', \'2\', \'3\'] } };\n    const dispatched: any[] = [];\n    getStuffMock.mockResolvedValueOnce(response as any);\n    await runSaga(\n      {\n        dispatch: (action) => dispatched.push(action),\n        getState: () => ({}),\n      },\n      getThemEffect,\n    ).toPromise();\n    expect(dispatched).toEqual([{ type: \'REQUEST\' }, { type: \'SUCCESS\', payload: [\'1\', \'2\', \'3\'] }]);\n    expect(getStuffMock).toHaveBeenCalled();\n  });\n});\n
Run Code Online (Sandbox Code Playgroud)\n

测试结果:

\n
 PASS   redux-saga-examples  packages/redux-saga-examples/src/stackoverflow/69371886/saga.test.ts\n  search saga\n    \xe2\x9c\x93 should create correct success flow (4 ms)\n    \xe2\x9c\x93 should pass (3 ms)\n\n----------|---------|----------|---------|---------|-------------------\nFile      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s \n----------|---------|----------|---------|---------|-------------------\nAll files |   80.95 |        0 |      60 |   78.57 |                   \n api.ts   |      50 |      100 |       0 |      50 | 4-8               \n saga.ts  |   88.24 |        0 |      75 |      90 | 14                \n----------|---------|----------|---------|---------|-------------------\nTest Suites: 1 passed, 1 total\nTests:       2 passed, 2 total\nSnapshots:   0 total\nTime:        4.662 s\n
Run Code Online (Sandbox Code Playgroud)\n