如何用 Jest 模拟 Axios?

新手上路,请多包涵

我在 client/index.js 中有一个函数,它使用 axios 发出请求

import axios from "axios";

const createRequest = async (url, method) => {
    const response = await axios({
        url: url,
        method: method
    });
    return response;
};

export default { createRequest };

我想使用 jest 测试这个功能,所以我创建了 client/index.test.js

 import { jest } from "@jest/globals";
import axios from "axios";

import client from "./";

jest.doMock('axios', () => jest.fn(() => Promise.resolve()));

describe("Client", () => {

    it("should call axios and return a response", async () => {
        const response = await client.createRequest('http://localhost/', 'GET');

        expect(axios).toHaveBeenCalled();
    })
})

但是当我尝试运行它时,测试失败了,我收到了这个错误

connect ECONNREFUSED 127.0.0.1:80

如果我使用模拟而不是 doMock,那么我会收到此错误 -

 ReferenceError: /Users/project/src/client/index.test.js: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
    Invalid variable access: jest

package.json -

 {
    "name": "project",
    "version": "0.0.1",
    "main": "index.js",
    "author": "author",
    "license": "MIT",
    "private": false,
    "type": "module",
    "scripts": {
        "start": "node --experimental-json-modules --experimental-specifier-resolution=node ./src/index.js",
        "start:dev": "nodemon --experimental-json-modules --experimental-specifier-resolution=node ./src/index.js",
        "test": "node --experimental-vm-modules node_modules/.bin/jest",
        "test:dev": "node --experimental-vm-modules node_modules/.bin/jest --watch",
        "test:coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage",
        "lint": "eslint --fix .",
        "pretty": "prettier --write ."
    },
    "dependencies": {
        "axios": "^0.21.1",
        "express": "^4.17.1"
    },
    "devDependencies": {
        "babel-eslint": "^10.1.0",
        "eslint": "^7.23.0",
        "jest": "^26.6.3",
        "prettier": "^2.2.1",
        "supertest": "^6.1.3"
    },
    "jest": { "testEnvironment": "node" }
}

我在节点环境中运行它,节点版本是 14.16.0 ,开玩笑的版本是 26.6.3 。请帮助确定这种方法有什么问题以及如何解决它。

原文由 doctorsherlock 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
2 个回答

我会推荐一种完全不同的方法来解决这个问题。与其尝试模拟 Axios,这是一个 您不拥有的 相对复杂的 API,不如使用 msw 等工具在网络边界进行测试。这使您可以自由重构实现 而无需 更改测试,让您更有信心它仍在工作。你可以这样做:

  • 将重复配置分解为 axios.create({ baseURL: "http://localhost", ... })
  • 为请求切换到不同的库(例如 node-fetch )。

此外,如果 Axios API 更改,您的测试将开始失败, _告诉您您的代码不再有效_。使用测试替身,因为它仍然会实现以前的 API,所以您会获得通过但具有误导性的测试结果。

这是那种测试的样子;请注意,根本没有提到 Axios,它现在只是一个实现细节,我们只关心 _行为_:

 import { rest } from "msw";
import { setupServer } from "msw/node";

import client from "./";

const body = { hello: "world" };

const server = setupServer(
  rest.get("http://localhost", (_, res, ctx) => {
    return res(ctx.status(200), ctx.json(body))
  })
);

describe("Client", () => {
    beforeAll(() => server.listen());

    afterEach(() => server.resetHandlers());

    afterAll(() => server.close());

    it("should call the API and return a response", async () => {
        const response = await client.createRequest("http://localhost/", "GET");

        expect(response).toMatchObject({ data: body, status: 200 });
    });
});

请注意,我不得不使用 .toMatchObject 因为您公开了整个 Axios 响应对象,其中包含很多属性。这对您的客户端来说不是一个好的 API,因为现在 所有 使用客户端的东西都在使用 Axios API;这使您与它严重耦合,并稀释了我上面提到的好处。

我不确定您打算如何使用它,但我倾向于完全隐藏传输层的细节——状态代码、标头等内容不太可能与消费者的业务逻辑相关。现在你真的只有:

 const createRequest = (url, method) => axios({ method, url });

在这一点上,您的消费者还不如直接使用 Axios。

原文由 jonrsharpe 发布,翻译遵循 CC BY-SA 4.0 许可协议

jest.doMock(moduleName, factory, options) 方法不会自动提升到代码块的顶部。这意味着 axios 函数中使用的 createRequest 函数仍将是原始函数。

您需要使用 jest.mock()

例如

index.js :

 import axios from 'axios';

const createRequest = async (url, method) => {
  const response = await axios({
    url: url,
    method: method,
  });
  return response;
};

export default { createRequest };

index.test.js :

 import axios from 'axios';
import client from './';

jest.mock('axios', () => jest.fn(() => Promise.resolve('teresa teng')));

describe('Client', () => {
  it('should call axios and return a response', async () => {
    const response = await client.createRequest('http://localhost/', 'GET');
    expect(axios).toHaveBeenCalled();
    expect(response).toEqual('teresa teng');
  });
});

单元测试结果:

  PASS  examples/67101502/index.test.js (11.503 s)
  Client
    ✓ should call axios and return a response (4 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |
 index.js |     100 |      100 |     100 |     100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        13.62 s

原文由 slideshowp2 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题