简体   繁体   English

如何在 Jest 中模拟 AbortController

[英]How to mock AbortController in Jest

I have a Redux saga that makes several API requests.我有一个Redux传奇,它发出了几个 API 请求。 I am using takeLatest to make sure that any previously running sagas are cancelled if a new action is fired.我正在使用takeLatest确保在触发新操作时取消任何以前运行的 sagas。 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):为了解决这个问题,我在 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);
}

However, I'm not sure how to check that abortController.abort() is called, since AbortController is instantiated inside the saga.但是,我不确定如何检查是否调用了abortController.abort() ,因为 AbortController 是在 saga 中实例化的。 Is there a way to mock this?有没有办法模拟这个?

You can use jest.spyOn(object, methodName) to create mock for AbortController.prototype.abort method.您可以使用jest.spyOn(object, methodName)AbortController.prototype.abort方法创建模拟。 Then, execute the saga generator, test it by each step.然后,执行 saga 生成器,按步骤测试。 Simulate the cancellation using gen.return() method.使用gen.return()方法模拟取消。

My test environment is node , so I use abortcontroller-polyfill to polyfill AbortController .我的测试环境是node ,所以我使用abortcontroller- polyfill 来AbortController

Eg例如

saga.ts : 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 : 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.为了测试AbortControllerabort function 我在我的测试中模拟了global.AbortController

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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