Jos*_*ker 5 javascript mocking typescript jestjs redux-saga
我有一个Redux发出多个 API 请求的传奇。我用来takeLatest确保如果触发新操作,任何先前运行的传奇都会被取消。但是,这不会取消正在进行的请求,并且我们遇到了最大连接限制问题。
为了解决这个问题,我在 saga 中创建一个 AbortController 并将其传递给每个请求,以便在取消 saga 时可以中止它们(见下文):
export function* doSomething(action: Action): SagaIterator {
const abortController = new AbortController();
try {
const fooResponse: FooResponse = yield call(getFoo, ..., abortController);
...
const barResponse: BarResponse = yield call(getBar, ..., abortController);
}
catch {
.. handle error
}
finally {
if (yield cancelled()) {
abortController.abort(); // Cancel the API call if the saga was cancelled
}
}
}
export function* watchForDoSomethingAction(): SagaIterator {
yield takeLatest('action/type/app/do_something', doSomething);
}
Run Code Online (Sandbox Code Playgroud)
但是,我不确定如何检查它abortController.abort()是否被调用,因为 AbortController 是在传奇中实例化的。有办法嘲笑这个吗?
小智 6
为了测试AbortController的abort功能,我模拟了global.AbortController我的测试内部。
例子:
const abortFn = jest.fn();
// @ts-ignore
global.AbortController = jest.fn(() => ({
abort: abortFn,
}));
await act(async () => {
// ... Trigger the cancel function
});
// expect the mock to be called
expect(abortFn).toBeCalledTimes(1);Run Code Online (Sandbox Code Playgroud)
您可以使用jest.spyOn(object, methodName)为AbortController.prototype.abort方法创建模拟。然后,执行saga生成器,每一步进行测试。使用方法模拟取消gen.return()。
我的测试环境是node,所以我使用abortcontroller-polyfill来进行 polyfill AbortController。
例如
\nsaga.ts:
import { AbortController, abortableFetch } from \'abortcontroller-polyfill/dist/cjs-ponyfill\';\nimport _fetch from \'node-fetch\';\nimport { SagaIterator } from \'redux-saga\';\nimport { call, cancelled, takeLatest } from \'redux-saga/effects\';\nconst { fetch } = abortableFetch(_fetch);\n\nexport function getFoo(abortController) {\n return fetch(\'http://localhost/api/foo\', { signal: abortController.signal });\n}\n\nexport function* doSomething(): SagaIterator {\n const abortController = new AbortController();\n try {\n const fooResponse = yield call(getFoo, abortController);\n } catch {\n console.log(\'handle error\');\n } finally {\n if (yield cancelled()) {\n abortController.abort();\n }\n }\n}\n\nexport function* watchForDoSomethingAction(): SagaIterator {\n yield takeLatest(\'action/type/app/do_something\', doSomething);\n}\nRun Code Online (Sandbox Code Playgroud)\nsaga.test.ts:
import { AbortController } from \'abortcontroller-polyfill/dist/cjs-ponyfill\';\nimport { call, cancelled } from \'redux-saga/effects\';\nimport { doSomething, getFoo } from \'./saga\';\n\ndescribe(\'66588109\', () => {\n it(\'should pass\', async () => {\n const abortSpy = jest.spyOn(AbortController.prototype, \'abort\');\n const gen = doSomething();\n expect(gen.next().value).toEqual(call(getFoo, expect.any(AbortController)));\n expect(gen.return!().value).toEqual(cancelled());\n gen.next(true);\n expect(abortSpy).toBeCalledTimes(1);\n abortSpy.mockRestore();\n });\n});\nRun Code Online (Sandbox Code Playgroud)\n测试结果:
\n PASS src/stackoverflow/66588109/saga.test.ts\n 66588109\n \xe2\x9c\x93 should pass (4 ms)\n\n----------|---------|----------|---------|---------|-------------------\nFile | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s \n----------|---------|----------|---------|---------|-------------------\nAll files | 75 | 50 | 33.33 | 78.57 | \n saga.ts | 75 | 50 | 33.33 | 78.57 | 8,16,25 \n----------|---------|----------|---------|---------|-------------------\nTest Suites: 1 passed, 1 total\nTests: 1 passed, 1 total\nSnapshots: 0 total\nTime: 2.801 s\nRun Code Online (Sandbox Code Playgroud)\n