前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JavaScript 测试系列实战(三):使用 Mock 模拟模块并处理组件交互

JavaScript 测试系列实战(三):使用 Mock 模拟模块并处理组件交互

作者头像
一只图雀
发布于 2020-09-10 02:56:18
发布于 2020-09-10 02:56:18
5.2K00
代码可运行
举报
文章被收录于专栏:图雀社区图雀社区
运行总次数:0
代码可运行

在之前的两篇教程中,我们学会了如何去测试最简单的 React 组件。在实际开发中,我们的组件经常需要从外部 API 获取数据,并且组件的交互逻辑也往往更复杂。在这篇教程中,我们将学习如何测试更复杂的组件,包括用 Mock 去编写涉及外部 API 的测试,以及通过 Enzyme 来轻松模拟组件交互

初次尝试 Jest Mock

我们的应用程序通常需要从外部的 API 获取数据。在编写测试时,外部 API 可能由于各种原因而失败。我们希望我们的测试是可靠和独立的,而最常见的解决方案就是 Mock。

改写 TodoList 组件

首先让我们改造组件,使其能够通过 API 获取数据。安装 axios:

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

然后改写 TodoList 组件如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/TodoList.js
import React, { Component } from 'react';
import axios from 'axios';

import Task from './Task';

const apiUrl = 'https://api.tuture.co';

class ToDoList extends Component {
  state = {
    tasks: [],
  };

  componentDidMount() {
    return axios
      .get(`${apiUrl}/tasks`)
      .then((tasksResponse) => {
        this.setState({ tasks: tasksResponse.data });
      })
      .catch((error) => console.log(error));
  }

  render() {
    return (
      <ul>
        {this.state.tasks.map((task) => (
          <Task key={task.id} id={task.id} name={task.name} />
        ))}
      </ul>
    );
  }
}

export default ToDoList;

TodoList 被改造成了一个“聪明组件”,在 componentDidMount 生命周期函数中通过 axios 模块异步获取数据。

编写 axios 模块的 mock 文件

Jest 支持对整个模块进行 Mock,使得组件不会调用原始的模块,而是调用我们预设的 Mock 模块。按照官方推荐,我们创建 mocks 目录并把 mock 文件放到其中。创建 axios 的 Mock 文件 axios.js,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/__mocks__/axios.js
'use strict';

module.exports = {
  get: () => {
    return Promise.resolve({
      data: [
        {
          id: 0,
          name: 'Wash the dishes',
        },
        {
          id: 1,
          name: 'Make the bed',
        },
      ],
    });
  },
};

这里的 axios 模块提供了一个 get 函数,并且会返回一个 Promise,包含预先设定的假数据。

通过 spyOn 函数检查 Mock 模块调用情况

让我们开始 Mock 起来!打开 TodoList 的测试文件,首先在最前面通过 jest.mock 配置 axios 模块的 Mock(确保要在 import TodoList 之前),在 Mock 之后,无论在测试还是组件中使用的都将是 Mock 版本的 axios。然后创建一个测试用例,检查 Mock 模块是否被正确调用。代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/TodoList.test.js
import React from 'react';
import { shallow, mount } from 'enzyme';
import axios from 'axios';

jest.mock('axios');

import ToDoList from './ToDoList';

describe('ToDoList component', () => {
  // ...

  describe('when rendered', () => {
    it('should fetch a list of tasks', () => {
      const getSpy = jest.spyOn(axios, 'get');
      const toDoListInstance = shallow(<ToDoList />);
      expect(getSpy).toBeCalled();
    });
  });
});

测试模块中一个函数是否被调用实际上是比较困难的,但是所幸 Jest 为我们提供了完整的支持。首先通过 jest.spyOn,我们便可以监听一个函数的使用情况,然后使用配套的 toBeCalled Matcher 来判断该函数是否被调用。整体代码十分简洁,同时也保持了很好的可读性。

如果你忘记了 Jest Matcher 的含义,推荐阅读本系列的第一篇教程。

迭代 TodoList 组件

一个实际的项目总会不断迭代,当然也包括我们的 TodoList 组件。对于一个待办事项应用来说,最重要的当然便是添加新的待办事项。

修改 TodoList 组件,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/TodoList.js
// ...
class ToDoList extends Component {
  state = {
    tasks: [],
    newTask: '',
  };

  componentDidMount() {
    // ...
      .catch((error) => console.log(error));
  }

  addATask = () => {
    const { newTask, tasks } = this.state;

    if (newTask) {
      return axios
        .post(`${apiUrl}/tasks`, { task: newTask })
        .then((taskResponse) => {
          const newTasksArray = [...tasks];
          newTasksArray.push(taskResponse.data.task);
          this.setState({ tasks: newTasksArray, newTask: '' });
        })
        .catch((error) => console.log(error));
    }
  };

  handleInputChange = (event) => {
    this.setState({ newTask: event.target.value });
  };

  render() {
    const { newTask } = this.state;
    return (
      <div>
        <h1>ToDoList</h1>
        <input onChange={this.handleInputChange} value={newTask} />
        <button onClick={this.addATask}>Add a task</button>
        <ul>
          {this.state.tasks.map((task) => (
            <Task key={task.id} id={task.id} name={task.name} />
          ))}
        </ul>
      </div>
    );
  }
}

export default ToDoList;

由于我们大幅改动了 TodoList 组件,我们需要更新快照:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
npm test -- -u

如果你不熟悉 Jest 快照测试,请回看本系列第二篇教程。

更新后的快照文件反映了我们刚刚做的变化:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ToDoList component when provided with an array of tasks should render correctly 1`] = `
<div>
  <h1>
    ToDoList
  </h1>
  <input
    onChange={[Function]}
    value=""
  />
  <button
    onClick={[Function]}
  >
    Add a task
  </button>
  <ul />
</div>
`;

在测试中模拟 React 组件的交互

在上面迭代的 TodoList 中,我们使用了 axios.post。这意味着我们需要扩展 axios 的 mock 文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/__mocks__/axios.js
'use strict';

let currentId = 2;

module.exports = {
  get: () => {
    return Promise.resolve({
      // ...
      ],
    });
  },
  post: (url, data) => {
    return Promise.resolve({
      data: {
        task: {
          name: data.task,
          id: currentId++,
        },
      },
    });
  },
};

可以看到上面,我们添加了一个 currentId 变量,因为我们需要保持每个 task 的唯一性。

让我们开始测试吧!我们测试的第一件事是检查修改输入值是否更改了我们的状态:

我们修改 app/components/TodoList.test.js 如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import { shallow } from 'enzyme';
import ToDoList from './ToDoList';

describe('ToDoList component', () => {
  describe('when the value of its input is changed', () => {
    it('its state should be changed', () => {
      const toDoListInstance = shallow(
        <ToDoList/>
      );

      const newTask = 'new task name';
      const taskInput = toDoListInstance.find('input');
      taskInput.simulate('change', { target: { value: newTask }});

      expect(toDoListInstance.state().newTask).toEqual(newTask);
    });
  });
});

这里要重点指出的就是 simulate[1] 函数的调用。这是我们几次提到的ShallowWrapper的功能。我们用它来模拟事件。它第一个参数是事件的类型(由于我们在输入中使用onChange,因此我们应该在此处使用change),第二个参数是模拟事件对象(event)。

为了进一步说明问题,让我们测试一下用户单击按钮后是否从我们的组件发送了实际的 post 请求。我们修改测试代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import { shallow } from 'enzyme';
import ToDoList from './ToDoList';
import axios from 'axios';

jest.mock('axios');

describe('ToDoList component', () => {
  describe('when the button is clicked with the input filled out', () => {
    it('a post request should be made', () => {
      const toDoListInstance = shallow(
        <ToDoList/>
      );
      const postSpy = jest.spyOn(axios, 'post');

      const newTask = 'new task name';
      const taskInput = toDoListInstance.find('input');
      taskInput.simulate('change', { target: { value: newTask }});

      const button = toDoListInstance.find('button');
      button.simulate('click');

      expect(postSpy).toBeCalled();
    });
  });
});

感谢我们的 mock 和 simulate 事件,测试通过了!现在事情会变得有些棘手。我们将测试状态是否随着我们的新任务而更新,其中比较有趣的是请求是异步的,我们继续修改代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import React from 'react';
import { shallow } from 'enzyme';
import ToDoList from './ToDoList';
import axios from 'axios';

jest.mock('axios');

describe('ToDoList component', () => {
  describe('when the button is clicked with the input filled out, the new task should be added to the state', () => {
    it('a post request should be made', () => {
      const toDoListInstance = shallow(
        <ToDoList/>
      );
      const postSpy = jest.spyOn(axios, 'post');

      const newTask = 'new task name';
      const taskInput = toDoListInstance.find('input');
      taskInput.simulate('change', { target: { value: newTask }});

      const button = toDoListInstance.find('button');
      button.simulate('click');

      const postPromise = postSpy.mock.results.pop().value;

      return postPromise.then((postResponse) => {
        const currentState = toDoListInstance.state();
        expect(currentState.tasks.includes((postResponse.data.task))).toBe(true);
      })
    });
  });
});

就像上面看到的,postSpy.mock.results 是 post 函数发送结果的数组,通过使用它,我们可以得到返回的 promise,我们可以从 value 属性中取到这个 promise。从测试返回 promise 是确保 Jest 等待其异步方法执行结束的一种方法。

小结

在本文中,我们介绍了 mock 模块,并将其用于伪造API调用。由于没有发起实际的 post 请求,我们的测试可以更可靠,更快。除此之外,我们还在整个 React 组件中模拟了事件。我们检查了它是否产生了预期的结果,例如组件的请求或状态变化。为此,我们了解了 spy 的概念。

尝试测试 React Hooks

Hooks 是 React 的一个令人兴奋的补充,毫无疑问,它可以帮助我们将逻辑与模板分离。这样做使上述逻辑更具可测试性。不幸的是,测试钩子并没有那么简单。在本文中,我们研究了如何使用 react-hooks-testing-library[2] 处理它。

我们创建 src/useModalManagement.js 文件如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/useModalManagement.js
import { useState } from 'react';

function useModalManagement() {
  const [isModalOpened, setModalVisibility] = useState(false);

  function openModal() {
    setModalVisibility(true);
  }

  function closeModal() {
    setModalVisibility(false);
  }

  return {
    isModalOpened,
    openModal,
    closeModal,
  };
}

export default useModalManagement;

上面的 Hooks 可以轻松地管理模式状态。让我们开始测试它是否不会引发任何错误,我们创建 useModalManagement.test.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/useModalManagement.test.js
import useModalManagement from './useModalManagement';

describe('The useModalManagement hook', () => {
  it('should not throw an error', () => {
    useModalManagement();
  });
});

我们运行测试,得到如下的结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FAIL useModalManagement.test.js
  The useModalManagement hook
    ✕ should not throw an error按 ⌘+↩ 退出

不幸的是,上述测试无法正常进行。我们可以通过阅读错误消息找出原因:

无效的 Hooks 调用, Hooks 只能在函数式组件的函数体内部调用。

让测试通过

React文档[3] 里面提到:我们只能从函数式组件或其他 Hooks 中调用 Hooks。我们可以使用本系列前面部分介绍的 enzyme 库来解决此问题,而且使了一点小聪明,我们创建 testHook.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/testHook.js
import React from 'react';
import { shallow } from 'enzyme';

function testHook(hook) {
  let output;
  function HookWrapper() {
    output = hook();
    return <></>;
  }
  shallow(<HookWrapper />);
  return output;
}

export default testHook;

我们继续迭代 useModalManagement.test.js,修改内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// src/useModalManagement.test.js
import useModalManagement from './useModalManagement';
import testHook from './testHook';

describe('The useModalManagement hook', () => {
  it('should not throw an error', () => {
    testHook(useModalManagement);
  });
});

我们允许测试,得到如下结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PASS useModalManagement.test.js
  The useModalManagement hook
    ✓ should not throw an error

好多了!但是,上述解决方案不是很好,并且不能为我们提供进一步测试 Hooks 的舒适方法。这就是我们使用 react-hooks-testing-library[4] 的原因,我们将在下一篇教程里讲解如何更加舒适的测试 React Hooks 的方法,敬请期待!

参考资料

[1]

simulate: https://enzymejs.github.io/enzyme/docs/api/ShallowWrapper/simulate.html

[2]

react-hooks-testing-library: https://wanago.io/2019/12/09/javascript-design-patterns-facade-react-hooks/

[3]

React文档: https://reactjs.org/docs/hooks-overview.html

[4]

react-hooks-testing-library: https://wanago.io/2019/12/09/javascript-design-patterns-facade-react-hooks/

- END -

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 图雀社区 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JavaScript测试教程–part 4:模拟 API 调用和模拟 React 组件交互[每日前端夜话0xEA]
今天,我们进一步测试 React 组件。它涉及模拟组件交互和模拟 API 调用。你将学到两种方法,开始吧!
疯狂的技术宅
2019/11/25
4.1K0
JavaScript测试教程–part 4:模拟 API 调用和模拟 React 组件交互[每日前端夜话0xEA]
JavaScript 测试系列实战(二):深层渲染和快照测试
在上一篇教程中,我们已经介绍了使用 Enzyme 测试 React 组件的基本知识。今天,我们将更深入地挖掘并学习如何测试组件的 Props,如何(以及为什么)使用 mount 函数,以及什么是 Jest 快照测试。
一只图雀
2020/08/31
2.3K0
JavaScript 测试教程–part 3:测试 props,挂载函数和快照测试[每日前端夜话0xE9]
在上一篇教程中,我介绍了使用 Enzyme 测试 React 组件的基础知识。今天,将进行更深入的研究,并学习如何测试 props,如何(以及为什么)使用 mount 函数以及什么是快照测试。开始吧!
疯狂的技术宅
2019/11/25
1.8K0
JavaScript 测试教程–part 3:测试 props,挂载函数和快照测试[每日前端夜话0xE9]
那些年错过的React组件单元测试(上)
关于前端单元测试,其实两年前我就已经关注了,但那时候只是简单的知道断言,想着也不是太难的东西,项目中也没有用到,然后就想当然的认为自己就会了。
前端森林
2021/04/12
5.4K0
JavaScript测试教程-part 2:引入 Enzyme 并测试 React 组件[每日前端夜话0xE8]
在本教程的第一篇中,我们简要介绍了单元测试的基础。这次要更进一步,使用 Enzyme 库测试 React。这样可以使你的程序将更加可靠,并且更加容易避免回归。我们在这里用了 Jest,不过 Enzyme 也可以与 Mocha 和 Chai 之类的库一起使用。
疯狂的技术宅
2019/11/15
1.5K0
干货 | 携程租车React Native单元测试实践
琨玮,携程高级前端开发工程师,从事React Native/Web前端的开发及维护工作,喜欢研究新技术。
携程技术
2020/02/18
6.6K0
干货 | 携程租车React Native单元测试实践
React 组件测试技巧
在这个页面上,我们将主要使用函数组件。然而,这些测试策略并不依赖于实现细节,它对于 class 组件也同样有效。
Fonkie
2019/08/31
5.1K0
Jest单元测试之旅—实践总结
维基百科对于单元测试的定义:是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
gary12138
2022/10/05
10.8K0
Jest单元测试之旅—实践总结
前端单元测试那些事
Jest 是 Facebook 开源的一款 JS 单元测试框架,它也是 React 目前使用的单元测试框架,目前vue官方也把它当作为单元测试框架官方推荐 。 目前除了 Facebook 外,Twitter、Airbnb 也在使用 Jest。Jest 除了基本的断言和 Mock 功能外,还有快照测试、实时监控模式、覆盖度报告等实用功能。 同时 Jest 几乎不需要做任何配置便可使用。
树酱
2020/07/03
4.7K0
前端单元测试那些事
前端自动化测试
本文主要是介绍基于React+Ant Design(以下用Antd表示Ant Design)的项目,在对于自己封装的,或者基于Antd封装的公共组件的自动化测试技术的选型和实践。
河马嘴不大
2022/12/24
2.1K0
前端自动化测试
React + Redux Testing Library 单元测试
谈任何东西都一定要有个上下文。你的论述不能是「因为单元测试有这些好处,所以我们要做单元测试」,而应该是「不做单元测试我们会遇到什么问题」,这样才能回答「为什么要写单元测试」的问题。那么我们谈论单元测试的上下文是什么呢?不做单元测试我们会遇到什么问题呢?上图为一个产品从 idea 分析、设计、开发、测试到交付并获取市场反馈的过程。
JimmyLv_吕靖
2021/03/03
2.5K0
React + Redux Testing Library 单元测试
Vue Test Utils处理异步行为
在 wrapper 上调用某些方法时,例如 trigger 和 setValue,你可能会注意到指南中的其他部分使用了 await。为什么需要这样做呢?
泯泷、
2024/07/16
2180
[译] React 测试驱动开发:从用户故事到产品
原文:https://www.toptal.com/react/tdd-react-user-stories-to-development
江米小枣
2020/08/10
3.4K0
[译] React 测试驱动开发:从用户故事到产品
React测试框架之enzyme
Enzyme是由Airbnb开源的一个React的JavaScript测试工具,使React组件的输出更加容易extrapolate 。Enzyme的API和jQuery操作DOM一样灵活易用,因为它使用的是cheerio库来解析虚拟DOM,而cheerio的目标则是做服务器端的jQuery。Enzyme兼容大多数断言库和测试框架,如chai、mocha、jasmine等。
xiangzhihong
2022/11/30
1.2K0
Jest + React Testing Library 单测总结
1、背景 以前还是学生的时候,有学习一门与测试相关的课程。那个时候,觉得测试就是写 test case,写断言,跑测试,以及查看 test case 的 coverage。整个流程和写法也不是特别难,所以就理所当然地觉得,写测试也不是特别难。 加上之前实际的工作中,也没有太多的写测试的经历,所以当自己需要对组件库补充单元测试的时候,发现并不能照葫芦画瓢来写单测。一时不知道该如何下手,也不知道如何编写有效的单测,人有点懵,于是就比较粗略地研究了一下前端组件单测。 1.1 单测的目的 在频繁的需求变动中可控地保
用户1097444
2022/06/29
4.9K0
Jest + React Testing Library 单测总结
使用 Jest 进行前端单元测试
目前 Jest 已经在 Facebook 开源的 React, React Native 等前端项目中被做为标配测试框架。下面简单介绍一些 Jest 比较有用的功能和用法。
QQ音乐技术团队
2018/01/31
5.9K0
使用 Jest 进行前端单元测试
如何自动化测试 React Native 项目 (下篇) - 单元测试
接着上篇的内容, 这篇文章会详细的介绍在 Glow 我们如何写单元测试, 以及在 React Native 中各个模块单元测试的详细实现方式。
Java帮帮
2019/05/17
3.5K0
如何自动化测试 React Native 项目 (下篇) - 单元测试
编写接口请求库单元测试与 E2E 测试的思考
最近在写适配 Mx Space Server 的 JS SDK。因为想写一个正式一点的库,以后真正能派的上用场的,所以写的时候尽量严谨一点。所以单测和 E2E 也是非常重要。
Innei
2021/12/28
1.1K0
【干货分享】微信小程序单元测试攻略
导语 本文作者是腾讯社交增值产品部高级前端工程师林毅雄,对前端开发领域颇有研究。接下来,本文将从测试框架、实战、覆盖率、踩坑等方面分享一下微信小程序的单元测试经验,希望能帮到大家。 01 写作初衷 大家先看看A公司与B公司的数据对比: 从上图可以看出,B公司的单元测试做的比较好,每百行error数也比A公司的项目低。 总体来说,单元测试有以下一些好处: 1,及早发现代码错误,提高代码质量和可维护性。 2,代码变更时可以快速进行检查。 然而要做好测试也有一定的困难: 1,花费时间长。 2,被测代码
WeTest质量开放平台团队
2021/12/17
3.1K0
[译] Vuex 之单元测试
原文:https://lmiller1990.github.io/vue-testing-handbook/testing-vuex.html
江米小枣
2020/06/15
3.5K0
推荐阅读
相关推荐
JavaScript测试教程–part 4:模拟 API 调用和模拟 React 组件交互[每日前端夜话0xEA]
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验