简体   繁体   中英

How to mock AbortController in Jest

I have a Redux saga that makes several API requests. I am using takeLatest to make sure that any previously running sagas are cancelled if a new action is fired. However this does not cancel in-flight requests and we are running into max connection limit issues.

To fix this I am creating an AbortController inside the saga and passing it to each request so they can be aborted if the saga is cancelled (see below):

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

However, I'm not sure how to check that abortController.abort() is called, since AbortController is instantiated inside the saga. Is there a way to mock this?

You can use jest.spyOn(object, methodName) to create mock for AbortController.prototype.abort method. Then, execute the saga generator, test it by each step. Simulate the cancellation using gen.return() method.

My test environment is node , so I use abortcontroller-polyfill to polyfill AbortController .

Eg

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

test result:

 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

In order to test the AbortController 's abort function I mocked the global.AbortController inside my test.

Example:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM