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

查看:116
本文介绍了如何用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" }
}

我正在节点环境中运行它,节点版本为 14.16.0 ,开玩笑的版本为 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天全站免登陆