如何用 Jest 模拟 Axios? [英] How to mock Axios with Jest?

查看:44
本文介绍了如何用 Jest 模拟 Axios?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a function in client/index.js which is using axios to make a request

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

I want to test this function using jest, so I created 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();
    })
})

但是当我尝试运行它时,测试失败并且我收到此错误

But when I try to run this, the test is failing and I am getting this error

connect ECONNREFUSED 127.0.0.1:80

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

If I use mock instead of doMock, then I am getting this error -

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" }
}

我在 node env 中运行它,node 版本是 14.16.0,jest 版本是 26.6.3.请帮助确定这种方法有什么问题以及如何解决.

I am running this in node env and node version is 14.16.0, jest version is 26.6.3. Please help to identify what is wrong in this approach and how to fix it.

推荐答案

我会推荐一种完全不同的方法来解决这个问题.与其尝试模拟 Axios,这是一个相对复杂的 API,您不拥有,而是使用像 msw.这使您可以自由地重构实现, 无需更改测试,让您更有信心它仍在工作.您可以执行以下操作:

I would recommend an entirely different way of approaching this. Rather than trying to mock Axios, which is a relatively complicated API that you don't own, test at the network boundary using a tool like msw. This allows you to freely refactor the implementation without needing to change the tests, giving you more confidence it's still working. You could do things like:

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

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

Also if the Axios API changed your tests would start failing, telling you your code no longer works. With a test double, as that would still implement the previous API, you'd have passing but misleading test results.

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

Here's how that kind of test might look; note that Axios isn't mentioned at all, it's just an implementation detail now and we only care about the behaviour:

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;这让你与它严重耦合,并削弱了我上面提到的好处.

Note I've had to use .toMatchObject because you're exposing the whole Axios response object, which contains a lot of properties. This isn't a good API for your client, because now everything using the client is consuming the Axios API; this makes you heavily coupled to it, and dilutes the benefits I mentioned above.

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

I'm not sure how you're planning to use it, but I'd be inclined to hide the details of the transport layer entirely - things like status codes, headers etc. are not likely relevant to the business logic in the consumer. Right now you really just have:

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

在这一点上,您的消费者不妨直接使用 Axios.

at which point your consumers might as well just be using Axios directly.

这篇关于如何用 Jest 模拟 Axios?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆