如何使用 Jest 和酶模拟 axios.CancelToken

How to mock axios.CancelToken using Jest and enzyme

我正在尝试对以下模块进行单元测试,但出现以下错误。有人可以在这里分享关于如何模拟 axios.CancelToken() 的任何想法吗?或者如何测试这部分?

service.js

let myCancelToken
export async function myTestFuncService (name) {
  if (myCancelToken) {
    myCancelToken.cancel()
  }
  myCancelToken = axios.CancelToken.source()
  try {
    return axios.get(`mypath/${name}`, {
      cancelToken: myCancelToken.token
    })
  } catch (e) {
    return e
  }
}

service.test.js

import * as myModule from './service.js'
import axios from 'axios'
jest.mock('axios')

it('test myTestFuncService', async () => {
    const testData = {
      data: {
        response: 'some test data'
      }
    }
    axios.get.mockImplementationOnce(() => Promise.resolve(testData))
    await expect(
      myModule.myTestFuncService('ABC')
    ).resolves.toBe(testData)
  })

错误:

TypeError: Cannot set property 'cancel' of undefined

您可以使用 jest.spyOn() 模拟 axios.CancelToken.source()axios.get() 方法。

另外,由于你在模块的范围内定义了变量myCancelToken,所以在请求模块并执行myTestFuncService方法后,myCancelToken会被赋值一个值,下次请求模块时,它将从 request.cache 中获取并影响后面的测试用例。因此,您需要使用jest.resetModules()清除模块的缓存,以确保不同的测试用例使用“新鲜”模块。

例如

service.js:

import axios from 'axios';

let myCancelToken;
export async function myTestFuncService(name) {
  if (myCancelToken) {
    myCancelToken.cancel();
  }
  myCancelToken = axios.CancelToken.source();
  try {
    return await axios.get(`mypath/${name}`, {
      cancelToken: myCancelToken.token,
    });
  } catch (e) {
    return e;
  }
}

service.test.js:

describe('68673646', () => {
  let mod;
  let axios;
  beforeEach(() => {
    jest.resetModules();
    mod = require('./service');
    axios = require('axios').default;
  });
  afterEach(() => {
    jest.restoreAllMocks();
  });
  test('should get name', async () => {
    const cancelTokenSource = { cancel: jest.fn(), token: { reason: { message: 'user canceled' } } };
    jest.spyOn(axios.CancelToken, 'source').mockReturnValueOnce(cancelTokenSource);
    jest.spyOn(axios, 'get').mockResolvedValueOnce('teresa teng');
    await mod.myTestFuncService('ABC');
    expect(axios.get).toBeCalledWith('mypath/ABC', { cancelToken: cancelTokenSource.token });
    expect(axios.CancelToken.source).toBeCalledTimes(1);
  });

  test('should cancel if cancel token exists', async () => {
    const cancelTokenSource = { cancel: jest.fn(), token: { reason: { message: 'user canceled' } } };
    jest.spyOn(axios.CancelToken, 'source').mockReturnValue(cancelTokenSource);
    jest.spyOn(axios, 'get').mockResolvedValue('teresa teng');
    await mod.myTestFuncService('ABC');
    expect(axios.get).toBeCalledWith('mypath/ABC', { cancelToken: cancelTokenSource.token });
    expect(axios.CancelToken.source).toBeCalledTimes(1);
    await mod.myTestFuncService('DEF');
    expect(cancelTokenSource.cancel).toBeCalledTimes(1);
    expect(axios.get).toBeCalledWith('mypath/DEF', { cancelToken: cancelTokenSource.token });
  });
});

测试结果:

 PASS  examples/68673646/service.test.js (7.34 s)
  68673646
    ✓ should get name (6360 ms)
    ✓ should cancel if cancel token exists (7 ms)

------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------|---------|----------|---------|---------|-------------------
All files   |    87.5 |      100 |     100 |    87.5 |                   
 service.js |    87.5 |      100 |     100 |    87.5 | 14                
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        7.82 s, estimated 8 s

我正在测试 cancelToken 是否可以像这样卸载组件:

test('Should stop pending request in case the component is unmounted', async () => {
            const { result, unmount } = renderHook(() =>
                // hook goes here
            );
    
            expect(result.current.cancelTokenSource.token.reason).not.toBeDefined();
    
            unmount();
    
            expect(result.current.cancelTokenSource.token.reason).toBeDefined();
        });

cancelToken 被取消后,原因字段被添加到对象中,这让我们可以像这样测试取消。 我们不会模拟令牌本身,但在某些情况下这也可能是一种方式。