Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Jest中Mock网络请求

Jest中Mock网络请求

作者头像
WindRunnerMax
发布于 2021-09-15 04:54:20
发布于 2021-09-15 04:54:20
3.5K00
代码可运行
举报
文章被收录于专栏:Czy‘s BlogCzy‘s Blog
运行总次数:0
代码可运行

Jest中Mock网络请求

最近需要将一个比较老的库修改为TS并进行单元测试,修改为TS还能会一点,单元测试纯粹是现学现卖了,初学Jest框架,觉得在单元测试中比较麻烦的就是测试网络请求,所以记录一下MockAxios发起网络请求的一些方式。初学两天的小白,如有问题还请指出。

描述

文中提到的示例全部在 jest-axios-mock-server仓库 中,直接使用包管理器安装就可以启动示例,例如通过yarn安装:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ yarn install

package.json中指定了一些命令,分别如下:

  • npm run build: rollup的打包命令。
  • npm run test:demo1: 简单地mock封装的网络请求库。
  • npm run test:demo2: 采用重新实现并hook的方式完成mock
  • npm run test:demo3: 使用Jest中的库完成demo2的实现。
  • npm run test:demo4-5: 启动一个node服务器,通过axiosproxy将网络请求进行代理,转发到启动的node服务器,通过设置好对应的单元测试请求与响应的数据,利用对应关系实现测试,也就是jest-axios-mock-server完成的工作。

在这里我们封装了一层axios,比较接近真实场景,可以查看test/demo/wrap-request.ts文件,实际上只是简单的在内部创建了一个axios实例,并且转发了一下响应的数据而已,test/demo/index.ts文件简单地导出了一个counter方法,这里对于这两个参数有一定的处理然后才发起网络请求,之后对于响应的数据也有一定的处理,只是为了模拟一下相关的操作而已。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/demo/wrap-request.ts
import axios, { AxiosRequestConfig } from "axios";

const instance = axios.create({
    timeout: 3000,
});

export const request = (options: AxiosRequestConfig): Promise<any> => {
    // do something wrap
    return instance.request(options).then(res => res.data);
};
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/demo/index.ts
import { request } from "./wrap-request";

export const counter = (id: number, number: number): Promise<{ result: number; msg: string }> => {
    const operate = number > 0 ? 1 : -1;
    return request({
        url: "https://www.example.com/api/setCounter",
        method: "POST",
        data: { id, operate },
    })
        .then(res => {
            if (res.result === 0) return { result: 0, msg: "success" };
            if (res.result === -100) return { result: -100, msg: "need login" };
            return { result: -999, msg: "fail" };
        })
        .catch(err => {
            return { result: -999, msg: "fail" };
        });
};

此处的Jest使用了JSDOM模拟的浏览器环境,在jest.config.js中配置的setupFiles属性中配置了启动文件test/config/setup.js,在此处初始化了JSDOM

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { JSDOM } from "jsdom";

const config = {
    url: "https://www.example.com/",
    domain: "example.com",
};
const dom = new JSDOM("", config);
global.document = dom.window.document;
global.document.domain = config.domain;
global.window = dom.window;
global.location = dom.window.location;

demo1: 简单Mock网络请求

test/demo1.test.js中进行了简单的mock处理,通过npm run test:demo1即可尝试运行,实际上是将包装axioswrap-request库进行了一个mock操作,在Jest启动时会进行编译,在这里将这个库mock掉后,所有在之后引入这个库的文件都是会获得mock后的对象,也就是说我们可以认为这个库已经重写了,重写之后的方法都是JESTMock Functions了,可以使用诸如mockReturnValue一类的函数进行数据模拟,关于Mock Functions可以参考https://www.jestjs.cn/docs/mock-functions

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/demo1.test.js
import { counter } from "./demo";
import { request } from "./demo/wrap-request";

jest.mock("./demo/wrap-request");

describe("Simple mock", () => {
    it("test success", () => {
        request.mockResolvedValue({ result: 0 });
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: 0, msg: "success" });
        });
    });

    it("test need login", () => {
        request.mockResolvedValue({ result: -100 });
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: -100, msg: "need login" });
        });
    });

    it("test something wrong", () => {
        request.mockResolvedValue({ result: 1111111 });
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: -999, msg: "fail" });
        });
    });
});

在这里我们完成了返回值的Mock,也就是说对于wrap-request库中的request返回的值我们都能进行控制了,但是之前也提到过对于传入的参数也有一定的处理,这部分内容我们还没有进行断言,所以对于这个我们同样需要尝试进行处理。

demo2: hook网络请求

demo2通过npm run test:demo2即可尝试运行,在上边提到了我们可以处理返回值的情况,但是没法断言输入的参数是否正确进行了处理,所以我们需要处理一下这种情况,所幸Jest提供了一种可以直接实现被Mock的函数库的方式,当然实际上Jest还提供了mockImplementation的方式,这个是在demo3中使用的方式,在这里我们重写了被mock的函数库,在实现的时候也可以使用jest.fn完成Implementations,这里通过在返回之前写入了一个hook函数,并且在各个test时再实现断言或者是指定返回值,这样就可以解决上述问题,实际上就是实现了JestMock FunctionsmockImplementation

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/demo2.test.js
import { counter } from "./demo";
import * as request from "./demo/wrap-request";

jest.mock("./demo/wrap-request", () => {
    let hook = () => ({ result: 0 });
    return {
        setHook: cb => (hook = cb),
        request: (...args) => {
            return new Promise(resolve => {
                resolve(hook(...args));
            });
        },
    };
});

describe("Simple mock", () => {
    it("test success", () => {
        request.setHook(() => ({ result: 0 }));
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: 0, msg: "success" });
        });
    });

    it("test need login", () => {
        request.setHook(() => ({ result: -100 }));
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: -100, msg: "need login" });
        });
    });

    it("test something wrong", () => {
        request.setHook(() => ({ result: 1111111 }));
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: -999, msg: "fail" });
        });
    });

    it("test param transform", () => {
        return new Promise(done => {
            request.setHook(({ data }) => {
                expect(data).toStrictEqual({ id: 1, operate: 1 });
                done();
                return { result: 0 };
            });
            counter(1, 1000);
        });
    });
});

demo3: 使用Jest的mockImplementation

demo3通过npm run test:demo3即可尝试运行,在demo2中的例子实际上是写复杂了,在JestMock FunctionsmockImplementation的实现,直接使用即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/demo3.test.js
import { counter } from "./demo";
import { request } from "./demo/wrap-request";

jest.mock("./demo/wrap-request");

describe("Simple mock", () => {
    it("test success", () => {
        request.mockImplementation(() => Promise.resolve({ result: 0 }));
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: 0, msg: "success" });
        });
    });

    it("test need login", () => {
        request.mockImplementation(() => Promise.resolve({ result: -100 }));
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: -100, msg: "need login" });
        });
    });

    it("test something wrong", () => {
        request.mockImplementation(() => Promise.resolve({ result: 1111111 }));
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: -999, msg: "fail" });
        });
    });

    it("test param transform", () => {
        return new Promise(done => {
            request.mockImplementation(({ data }) => {
                expect(data).toStrictEqual({ id: 1, operate: 1 });
                done();
                return Promise.resolve({ result: 0 });
            });
            counter(1, 1000);
        });
    });
});

demo4-5: 真实发起网络请求

demo4demo5通过npm run test:demo4-5即可尝试运行,采用这种方式是进行了真正的数据请求,在这里会利用axios的代理,将内部的数据请求转发到指定的服务器端口,当然这个服务器也是在本地启动的,通过指定对应的path相关的请求与响应数据进行测试,如果请求的数据不正确,则不会正常匹配到相关的响应数据,这样这个请求会直接返回500,返回的响应数据如果不正确的话也会在断言时被捕捉。在这里就使用到了jest-axios-mock-server库,首先我们需要指定三个文件,分别对应每个单元测试文件启动前执行,Jest测试启动前执行,与Jest测试完成后执行的三个生命周期进行的操作,分别是jest.config.js配置文件的setupFilesglobalSetupglobalTeardown三个配置项。 首先是setupFiles,在这里我们除了初始化JSDOM之外,还需要对axios的默认代理进行操作,因为采用的方案是使用axiosproxy进行数据请求的转发,所以才需要在单元测试的最前方设定代理值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/config/setup.js
import { JSDOM } from "jsdom";
import { init } from "../../src/index";
import axios from "axios";

const config = {
    url: "https://www.example.com/",
    domain: "example.com",
};
const dom = new JSDOM("", config);
global.document = dom.window.document;
global.document.domain = config.domain;
global.window = dom.window;
global.location = dom.window.location;

init(axios);

之后便是globalSetupglobalTeardown两个配置项,在这里指的是Jest单元测试启动前与全部测试完毕后进行的操作,我们将服务器启动与关闭的操作都放在这里,请注意,在这两个文件运行的文件是单独的一个独立context,与任何进行的单元测试的context都是无关的,包括setupFiles配置项指定的文件,所以在此处所有的数据要么是通过在配置文件中指定,要不就是通过网络在服务器端口之间进行传输。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/config/global-setup.js
import { run } from "../../src";
export default async () => {
    await run();
};
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/config/global-teardown.js
import { close } from "../../src";
export default async function () {
    await close();
}

对于配置端口与域名信息,将其直接放置在jest.config.js中的globals字段中了,对于debug这个配置项,建议和test.only配合使用,在调用服务器信息的过程中可以打印出相关的请求信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// jest.config.js
module.exports = {
    // ...
    globals: {
        host: "127.0.0.1",
        port: "5000",
        debug: false,
    },
    // ...
}

当然,或许会有提出为什么不在每个单元测试文件的beforeAllafterAll生命周期启动与关闭服务器,首先这个方案我也尝试过,首先对于每个测试文件将服务器启动结束后再关闭虽然相对比较耗费时间,但是理论上还是合理的,毕竟要进行数据隔离的话确实是没错,但是在afterAll关闭的时候就出了问题,因为node服务器在关闭时调用的close方法并不会真实地关闭服务器以及端口占用,他只是停止处理请求了,端口还是被占用,当启动第二个单元测试文件时会抛出端口正在被占用的异常,虽然现在已经有一些解决的方案,但是我尝试过后并不理想,会偶现端口依旧被占用的情况,尤其是在node开机后第一次被运行的情况,异常的概率比较大,所以效果不是很理想,最终还是采用了这种完全隔离的方案,具体相关的问题可以参考https://stackoverflow.com/questions/14626636/how-do-i-shutdown-a-node-js-https-server-immediately。 由于采用的是完全隔离的方案,所以我们想给测试的请求进行请求与响应数据的传输的时候,只有两个方案,要么在服务器启动的时候,也就是test/config/global-setup.js文件中将数据全部指定完成,要么就是通过网络进行数据传输,即在服务器运行的过程中通过指定path然后该path的网络请求会携带数据,在服务器的闭包中会把这个数据请求指定,当然在这里两种方式都支持,我觉得还是在每个单元测试文件中指定一个自己的数据比较合适,所以在这里仅示例了在单元测试文件中指定要测试的数据。关于要测试的数据,指定了一个DataMapper类型,以减少类型出错导致的异常,在这里示例了两个数据集,另外在匹配querydata时是支持正则表达式的,对于DataMapper类型的结构还是比较标准的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/data/demo1.data.ts
import { DataMapper } from "../../src";

const data: DataMapper = {
    "/api/setCounter": [
        {
            request: {
                method: "POST",
                data: '{"id":1,"operate":1}',
            },
            response: {
                status: 200,
                json: {
                    result: 0,
                },
            },
        },
        {
            request: {
                method: "POST",
                data: /"id":2,"operate":-1/,
            },
            response: {
                status: 200,
                json: {
                    result: -100,
                },
            },
        },
    ],
};

export default data;
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/data/demo2.data.ts
import { DataMapper } from "../../src";

const data: DataMapper = {
    "/api/setCounter": [
        {
            request: {
                method: "POST",
                data: /"id":3,"operate":-1/,
            },
            response: {
                status: 200,
                json: {
                    result: -100,
                },
            },
        },
    ],
};

export default data;

最后进行的两个单元测试中就在beforeAll中指定了要测试的数据,要注意这里是return setSuitesData(data),因为要在数据设置成功响应以后在进行单元测试,之后就是正常的请求与响应以及断言测试是否正确了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/demo4.test.js
import { counter } from "./demo";
import { setSuitesData } from "../src/index";
import data from "./data/demo1.data";

beforeAll(() => {
    return setSuitesData(data);
});

describe("Simple mock", () => {
    it("test success", () => {
        return counter(1, 2).then(res => {
            expect(res).toStrictEqual({ result: 0, msg: "success" });
        });
    });

    it("test need login", () => {
        return counter(2, -3).then(res => {
            expect(res).toStrictEqual({ result: -100, msg: "need login" });
        });
    });
});
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// test/demo5.test.js
import { counter } from "./demo";
import { setSuitesData } from "../src/index";
import data from "./data/demo2.data";

beforeAll(() => {
    return setSuitesData(data);
});

describe("Simple mock", () => {
    it("test success", () => {
        return counter(3, -30).then(res => {
            expect(res).toStrictEqual({ result: -100, msg: "need login" });
        });
    });

    it("test no match response", () => {
        return counter(6, 2).then(res => {
            expect(res).toStrictEqual({ result: -999, msg: "fail" });
        });
    });
});

BLOG

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://github.com/WindrunnerMax/EveryDay/

参考

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://www.jestjs.cn/docs/mock-functions
https://stackoverflow.com/questions/41316071/jest-clean-up-after-all-tests-have-run
https://stackoverflow.com/questions/14626636/how-do-i-shutdown-a-node-js-https-server-immediately
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-09-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
前端自动化测试实践03—jest异步处理&mock
一般项目代码中会有不少异步 ajax 请求,例如测试下面 async.js 中的代码
CS逍遥剑仙
2019/10/31
5.4K0
使用 Jest 进行前端单元测试
目前 Jest 已经在 Facebook 开源的 React, React Native 等前端项目中被做为标配测试框架。下面简单介绍一些 Jest 比较有用的功能和用法。
QQ音乐技术团队
2018/01/31
5.9K0
使用 Jest 进行前端单元测试
那些年错过的React组件单元测试(上)
关于前端单元测试,其实两年前我就已经关注了,但那时候只是简单的知道断言,想着也不是太难的东西,项目中也没有用到,然后就想当然的认为自己就会了。
前端森林
2021/04/12
5.4K0
Jest + React Testing Library 单测总结
1、背景 以前还是学生的时候,有学习一门与测试相关的课程。那个时候,觉得测试就是写 test case,写断言,跑测试,以及查看 test case 的 coverage。整个流程和写法也不是特别难,所以就理所当然地觉得,写测试也不是特别难。 加上之前实际的工作中,也没有太多的写测试的经历,所以当自己需要对组件库补充单元测试的时候,发现并不能照葫芦画瓢来写单测。一时不知道该如何下手,也不知道如何编写有效的单测,人有点懵,于是就比较粗略地研究了一下前端组件单测。 1.1 单测的目的 在频繁的需求变动中可控地保
用户1097444
2022/06/29
4.9K0
Jest + React Testing Library 单测总结
编写接口请求库单元测试与 E2E 测试的思考
最近在写适配 Mx Space Server 的 JS SDK。因为想写一个正式一点的库,以后真正能派的上用场的,所以写的时候尽量严谨一点。所以单测和 E2E 也是非常重要。
Innei
2021/12/28
1.1K0
【架构师(第二十七篇)】前端单元测试框架 Jest 基础知识入门
如果使用的是 vscode 并且安装了 jest 插件,那么可以实时并且直观的看到测试是否通过
一尾流莺
2022/12/10
1.4K0
【架构师(第二十七篇)】前端单元测试框架 Jest 基础知识入门
用Jest来给React完成一次妙不可言的~单元测试
在2020的今天,构建一个 web 应用对于我们来说,并非什么难事。因为有很多足够多优秀的的前端框架(比如 React,Vue 和 Angular);以及一些易用且强大的UI库(比如 Ant Design)为我们保驾护航,极大地缩短了应用构建的周期。
用户1462769
2020/03/30
15.4K0
Vue 应用单元测试的策略与实践 02 - 单元测试基础
在上一篇文章当中我们介绍了单元测试的意义,以及为何选择 Facebook 的 Jest 作为我们的测试框架。现在就让我们一起来学习如何编写最基础的单元测试。
JimmyLv_吕靖
2019/09/10
2.4K0
Vue 应用单元测试的策略与实践 02 - 单元测试基础
web前端好帮手 - Jest单元测试工具
本文介绍如何使用Jest覆盖Web前端单元测试、如何统计测试覆盖率,Jest对比Mocha等内容。 Jest是什么? Jest是一个令人愉快的 JavaScript 测试框架,专注于简洁明快。 正如官方介绍所说,Jest是一款开箱即用的测试框架,其中包含了Expect断言接口、Mock接口、Snapshot快照、测试覆盖率统计等等全套测试功能。 为什么不推荐Mocha? 不支持原生并行测试 断言库要另外安装 测试覆盖率统计功能要另外安装 原生输入的测试报告可读性很差,格式化也要另外安装 不支持snap
QQ音乐技术团队
2020/06/15
5.3K0
React + Redux Testing Library 单元测试
谈任何东西都一定要有个上下文。你的论述不能是「因为单元测试有这些好处,所以我们要做单元测试」,而应该是「不做单元测试我们会遇到什么问题」,这样才能回答「为什么要写单元测试」的问题。那么我们谈论单元测试的上下文是什么呢?不做单元测试我们会遇到什么问题呢?上图为一个产品从 idea 分析、设计、开发、测试到交付并获取市场反馈的过程。
JimmyLv_吕靖
2021/03/03
2.5K0
React + Redux Testing Library 单元测试
Jest 单元测试快速上手指南
执行 yarn jest 或者 yarn jest test/plus.spec.js 运行测试用例
木子星兮
2020/08/25
3.6K0
Jest 单元测试快速上手指南
Jest单元测试之旅—实践总结
维基百科对于单元测试的定义:是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
gary12138
2022/10/05
10.8K0
Jest单元测试之旅—实践总结
Jest基本使用方法以及mock技巧介绍
Jest是由Facebook开发并维护的一套js的单元测试框架,之前在后台的nodejs项目里面第一次尝试使用,感觉还是非常容易上手的,功能也比较强大。
腾讯移动品质中心TMQ
2018/06/26
8.8K0
前端接入单元测试(Node+React)
假如要重构一个老前端框架,并根据其开发一个向后兼容的新框架。此时老框架针对其内部API函数,写了充分的单侧用例。在开发新框架时,直接运行老前端框架的单侧用例,如果所有测试用例都通过,则可快速保证内部api的一致性,快速验证所有功能。
kiki.
2022/09/29
3.6K0
前端接入单元测试(Node+React)
React单元测试:Jest + Enzyme(二)
在上一篇教程中,我们成功搭建了基于Jest和Enzyme的单元测试框架并成功地跑起来第一个单元测试,可以点击这里回顾一下。今天,我们重点讨论如何通过Jest来mock数据。
Dickensl
2022/06/14
1.6K0
React单元测试:Jest + Enzyme(二)
单元测试
测试的目的是为了带给我们带来强大的代码信心,如果把测试初衷忘掉,会很容易掉入测试代码细节的陷阱。一旦关注点不是代码的信心,而是测试代码细节,那么测试用例会变得非常脆弱,难以维护。
用户4619307
2024/01/12
8030
使用jest进行单元测试
不扯犊子直接说吧,第一点,用数据、用茫茫多的测试用例去告诉使用者,你的程序是多么鲁棒健壮;第二点,把它作为一种素养去培养吧,当你按照一系列规范去做事,那么你做出来的东西,我想是有品质在的。
江涛学编程
2020/08/25
3.9K0
使用jest进行单元测试
测试中如何处理 Http 请求?
不知道大家平时写单测时是怎么处理 网络请求 的,可能有的人会说:“把请求函数 Mock ,返回 Mock 结果就行了呀”。
写代码的海怪
2022/08/31
1.4K0
前端单元测试之Jest
关于前端单元测试的好处自不必说,基础的介绍和知识可以参考之前的博客链接:React Native单元测试。在软件的测试领域,测试主要分为:单元测试、集成测试和功能测试。
xiangzhihong
2022/11/30
3K0
一杯茶的时间,上手 Jest 测试框架
现在让我们正式开始,茶和图雀社区精心准备的甜品更搭哦。 在项目根目录下新建src目录,存放我们的功能代码。然后创建src/dessert.js。
一只图雀
2020/04/13
2K0
相关推荐
前端自动化测试实践03—jest异步处理&mock
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验