【问题标题】:axios / jest - unabled to perform a call request (TypeError: Cannot read property 'then' of undefined)axios / jest - 无法执行呼叫请求(TypeError:无法读取未定义的属性'then')
【发布时间】:2021-09-10 18:35:06
【问题描述】:

我正在努力执行一个关于 axios api 调用的测试

这是我的 API 调用,它在我的程序中完美运行

import axios from 'axios';
import crypto from 'crypto';
import { prop } from 'ramda';

const baseUrl = 'http://gateway.marvel.com:80';
const uri = '/v1/public/characters';
const charactersUrl = baseUrl + uri;

const timestamp = [Math.round(+new Date() / 1000)];
const privateApi = 'XXX';
const publicApi = 'XXX';

const concatenatedString = timestamp.concat(privateApi, publicApi).join('');

const hash = crypto.createHash('md5').update(`${concatenatedString}`).digest('hex');

const charactersApi = () =>
  axios
    .get(charactersUrl, {
      params: {
        ts: timestamp,
        apikey: publicApi,
        hash,
      },
    })
    .then(prop('data'));

export default charactersApi;

当我尝试测试它时,这样:

import axiosMock from 'axios';
import charactersApi from '../marvelApi';

jest.mock('axios', () => ({
  get: jest.fn(),
}));

describe('tools | marvelApi', () => {
  const piece = { name: '3D-MAN' };
  axiosMock.get.mockResolvedValueOnce({ data: piece });

  it('should get the character', () => {
    return charactersApi().then(elem => {
      expect(elem.name).toEqual('3D-MAN');
    });
  });
});

我从 jest 收到以下消息

    TypeError: Cannot read property 'then' of undefined

      16 |
      17 | const charactersApi = () =>
    > 18 |   axios
         |   ^
      19 |     .get(charactersUrl, {
      20 |       params: {
      21 |         ts: timestamp,

      at charactersApi (src/tools/marvelApi.js:18:3)
      at Object.<anonymous> (src/tools/tests/marvelApi.test.js:13:12)

我的尝试

  • 一个常见的错误是忘记了包含请求 API 的函数中的 return 语句,在我的例子中它是正确完成的(第一段代码 -> charactersApi())source1source2
  • 我还尝试从模拟的 Axios 返回一个 Promise,正如我在另一张 SO 票证上看到的那样
jest.mock('axios', () => ({
  get: jest.fn(() => Promise.resolve()),
}));

我认为我的 axios mock 不正确,因为斗争来自测试,而生产版本运行良好

有什么想法吗?

【问题讨论】:

  • 永远不要暴露你的 api 密钥!我已经为你删除了。
  • 谢谢,一开始我已经部分去掉了私钥,我觉得够了

标签: axios jestjs


【解决方案1】:

您可以监视“axios.get”调用并将它们解析为固定(模拟)值:

/**
 * @jest-environment jsdom
 */
const axios = require('axios')

beforeAll(() => {
  jest.spyOn(axios, 'get').mockImplementation()
})

afterAll(() => {
  jest.restoreAllMocks()
})

it('returns the mocked response', async () => {
  axios.get.mockResolvedValue({ data: 'foo' })
  const res = await axios.get('https://api.github.com')
  expect(res).toEqual({ data: 'foo' })
})

您不应使用jest.mock,因为它模拟了您导入的代码可能正在使用的模块。据我所知,它不会影响当前模块的导入(并且您导入 axios 作为测试的一部分)。

推荐解决方案

我强烈建议您不要直接监视/嘲笑 axios。请参阅下面的论证。

  1. 你在嘲笑axios 的实现细节。换句话说,您将axios.get 函数连同它可能具有的任何内部逻辑一起扔掉,并用硬模拟替换它。这意味着您的测试不再使用axios,而是使用axios 的空模拟外壳。这会使您的测试与实际代码不同,进而降低此类测试给您带来的信心。

  2. 您将模拟与特定请求客户端 (axios) 耦合。这种方法不是长期投资,因为您正在编写特定于 axios 的模拟。您不能对其他客户端(即window.fetch、Apollo 等)发出的请求重用此类模拟,因为它们有自己的实现细节(即window.fetch 没有.get() 可以监视),这只会鼓励您可以在测试中编写更多特定于实现的逻辑。

您可以在 Kent C. Dodds 的 Stop mocking fetch 文章中详细了解直接模拟请求客户端的缺点。它以window.fetch mocks 为例,但您可以在阅读时将其替换为ANY_REQUEST_CLIENT

我强烈建议使用像 Mock Service Worker (MSW) 这样的工具,它会鼓励您编写不依赖任何请求客户端的抽象模拟(无论您的测试代码如何发出请求,您都可以使用它们),甚至可以可以在不同的测试级别重用(Jest、Storybook 或 Cypress 的相同模拟)。

以下是使用 MSW 进行的测试:

import { rest } from 'msw'
import { setupServer } from 'msw/node'
import charactersApi from '../marvelApi';

const server = setupServer(
  rest.get('http://gateway.marvel.com:80/v1/public/characters', (req, res, ctx) => {
    return res(ctx.json({
      data: {
        name: '3D-MAN'
      }
    }))
  })
)

beforeAll(() => server.listen()
afterAll(() => server.close())

describe('tools | marvelApi', () => {
  it('should get the character', () => {
    return charactersApi().then(elem => {
      expect(elem.name).toEqual('3D-MAN')
    })
  })
})

请注意,没有关于如何发出请求的详细信息,只有哪个请求拦截和模拟其响应。

您可以按照详细教程了解如何Get started with MSW。还有一个great video关于 API mocking 和 MSW 解决了什么问题。


【讨论】:

  • 我从你的帖子中学到了很多,从现在开始我将深入研究 MSW 谢谢你@kettanaito
  • 我看过你链接的视频。很清楚,但是我有以下问题:在我的情况下,api 调用的成功归功于 en API 密钥(用于对我进行身份验证),如果我说 msw 与API 密钥是否有效。换句话说,测试可能会在生产中失败,因为 API 密钥不正确,因为测试会成功
  • API 密钥有效性不是集成测试的关注点(您在问题中说明了这一点)。我认为这也不是 E2E 测试的问题,但这值得商榷。不过,这不是坏事。建立明确的测试边界是进行重点测试的原因。
  • 由于验证 API 密钥的通常是第三方系统,因此它们的有效性在您的应用程序代码之外。除了与该系统的交互之外,您不应该以任何方式测试第三方系统的可操作性。我建议在 E2E/canary 测试中考虑 API 密钥有效性(即执行实际授权等)。
  • 明白了,谢谢你的解释
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-19
  • 2014-09-07
  • 1970-01-01
  • 2021-12-10
  • 1970-01-01
  • 2014-11-26
相关资源
最近更新 更多