本篇文章为大家展示了JavaScript中如何使用Mock模拟模块并处理组件交互,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
我们将学习如何测试更复杂的组件,包括用 Mock 去编写涉及外部 API 的测试,以及通过 Enzyme 来轻松模拟组件交互
我们的应用程序通常需要从外部的 API 获取数据。在编写测试时,外部 API 可能由于各种原因而失败。我们希望我们的测试是可靠和独立的,而最常见的解决方案就是 Mock。
首先让我们改造组件,使其能够通过 API 获取数据。安装 axios:
npm install axios
然后改写 TodoList
组件如下:
// src/TodoList.jsimport 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
模块异步获取数据。
Jest 支持对整个模块进行 Mock,使得组件不会调用原始的模块,而是调用我们预设的 Mock 模块。按照官方推荐,我们创建 mocks 目录并把 mock 文件放到其中。创建 axios 的 Mock 文件 axios.js,代码如下:
// 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,包含预先设定的假数据。
让我们开始 Mock 起来!打开 TodoList 的测试文件,首先在最前面通过 jest.mock
配置 axios 模块的 Mock(确保要在 import TodoList
之前),在 Mock 之后,无论在测试还是组件中使用的都将是 Mock 版本的 axios。然后创建一个测试用例,检查 Mock 模块是否被正确调用。代码如下:
// src/TodoList.test.jsimport 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 组件,代码如下:
// 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> <h2>ToDoList</h2> <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 组件,我们需要更新快照:
npm test -- -u
如果你不熟悉 Jest 快照测试,请回看本系列第二篇教程。
更新后的快照文件反映了我们刚刚做的变化:
// Jest Snapshot v1, https://goo.gl/fbAQLPexports[`ToDoList component when provided with an array of tasks should render correctly 1`] = `<div> <h2> ToDoList </h2> <input onChange={[Function]} value="" /> <button onClick={[Function]} > Add a task </button> <ul /></div>`;
在上面迭代的 TodoList 中,我们使用了 axios.post。这意味着我们需要扩展 axios 的 mock 文件:
// 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
如下:
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 请求。我们修改测试代码如下:
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 事件,测试通过了!现在事情会变得有些棘手。我们将测试状态是否随着我们的新任务而更新,其中比较有趣的是请求是异步的,我们继续修改代码如下:
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 的概念。
Hooks 是 React 的一个令人兴奋的补充,毫无疑问,它可以帮助我们将逻辑与模板分离。这样做使上述逻辑更具可测试性。不幸的是,测试钩子并没有那么简单。在本文中,我们研究了如何使用 react-hooks-testing-library[2] 处理它。
我们创建 src/useModalManagement.js
文件如下:
// src/useModalManagement.jsimport { 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
// src/useModalManagement.test.jsimport useModalManagement from './useModalManagement';describe('The useModalManagement hook', () => { it('should not throw an error', () => { useModalManagement(); });});
我们运行测试,得到如下的结果:
FAIL useModalManagement.test.js The useModalManagement hook ✕ should not throw an error按 ⌘+↩ 退出
不幸的是,上述测试无法正常进行。我们可以通过阅读错误消息找出原因:
无效的 Hooks 调用, Hooks 只能在函数式组件的函数体内部调用。
React文档[3] 里面提到:我们只能从函数式组件或其他 Hooks 中调用 Hooks。我们可以使用本系列前面部分介绍的 enzyme 库来解决此问题,而且使了一点小聪明,我们创建 testHook.js
:
// src/testHook.jsimport 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
,修改内容如下:
// src/useModalManagement.test.jsimport useModalManagement from './useModalManagement';import testHook from './testHook';describe('The useModalManagement hook', () => { it('should not throw an error', () => { testHook(useModalManagement); });});
我们允许测试,得到如下结果:
PASS useModalManagement.test.js The useModalManagement hook ✓ should not throw an error
好多了!但是,上述解决方案不是很好,并且不能为我们提供进一步测试 Hooks 的舒适方法。这就是我们使用 react-hooks-testing-library[4] 的原因。
上述内容就是JavaScript中如何使用Mock模拟模块并处理组件交互,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注亿速云行业资讯频道。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/u/4088983/blog/4544477