如何在 Jest 中模拟 AbortController

时间:2021-03-11 18:07:17

标签: javascript typescript jestjs mocking 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);
}

但是,我不确定如何检查 abortController.abort() 是否被调用,因为 AbortController 是在 saga 中实例化的。有没有办法模拟这个?

1 个答案:

答案 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
相关问题