我有一个 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);
}
但是,我不确定如何检查 abortController.abort()
是否被调用,因为 AbortController 是在 saga 中实例化的。有没有办法模拟这个?
答案 0 :(得分:0)
您可以使用 jest.spyOn(object, methodName) 为 AbortController.prototype.abort
方法创建模拟。然后,执行 saga 生成器,按每个步骤对其进行测试。使用 gen.return()
方法模拟取消。
我的测试环境是 node
,所以我使用 abortcontroller-polyfill 来填充 AbortController
。
例如
saga.ts
:
import { AbortController, abortableFetch } from 'abortcontroller-polyfill/dist/cjs-ponyfill';
import _fetch from 'node-fetch';
import { SagaIterator } from 'redux-saga';
import { call, cancelled, takeLatest } from 'redux-saga/effects';
const { fetch } = abortableFetch(_fetch);
export function getFoo(abortController) {
return fetch('http://localhost/api/foo', { signal: abortController.signal });
}
export function* doSomething(): SagaIterator {
const abortController = new AbortController();
try {
const fooResponse = yield call(getFoo, abortController);
} catch {
console.log('handle error');
} finally {
if (yield cancelled()) {
abortController.abort();
}
}
}
export function* watchForDoSomethingAction(): SagaIterator {
yield takeLatest('action/type/app/do_something', doSomething);
}
saga.test.ts
:
import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill';
import { call, cancelled } from 'redux-saga/effects';
import { doSomething, getFoo } from './saga';
describe('66588109', () => {
it('should pass', async () => {
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
const gen = doSomething();
expect(gen.next().value).toEqual(call(getFoo, expect.any(AbortController)));
expect(gen.return!().value).toEqual(cancelled());
gen.next(true);
expect(abortSpy).toBeCalledTimes(1);
abortSpy.mockRestore();
});
});
测试结果:
PASS src/stackoverflow/66588109/saga.test.ts
66588109
✓ should pass (4 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 75 | 50 | 33.33 | 78.57 |
saga.ts | 75 | 50 | 33.33 | 78.57 | 8,16,25
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.801 s