每日前端夜话,陪你聊前端。
每天晚上18:00准时推送。
正文共:1822 字
预计阅读时间:6 分钟
作者:Marcin Wanago
翻译:疯狂的技术宅
来源:wanago.io
-
4. JavaScript测试教程–part 4:模拟 API 调用和模拟 React 组件交互
今天,我们进一步测试 React 组件。它涉及模拟组件交互和模拟 API 调用。你将学到两种方法,开始吧!
模拟
对于我们的程序来说,从 API 获取一些数据是很常见的。但是它可能由于各种原因而失败,例如 API 被关闭。我们希望测试可靠且独立,并确保可以模拟某些模块。我们把 ToDoList 组件修改为智能组件。
app/components/ToDoList.component.js
1import React, { Component } from 'react';
2import Task from "../Task/Task";
3import axios from 'axios';
4
5class ToDoList extends Component {
6 state = {
7 tasks: []
8 }
9 componentDidMount() {
10 return axios.get(`${apiUrl}/tasks`)
11 .then(tasksResponse => {
12 this.setState({
13 tasks: tasksResponse.data
14 })
15 })
16 .catch(error => {
17 console.log(error);
18 })
19 }
20 render() {
21 return (
22 <div>
23 <h1>ToDoList</h1>
24 <ul>
25 {
26 this.state.tasks.map(task =>
27 <Task key={task.id} id={task.id} name={task.name}/>
28 )
29 }
30 </ul>
31 </div>
32 )
33 }
34}
35
36export default ToDoList;
37
它使用 axios 提取数据,所以需要模拟该模块,因为我们不希望发出实际的请求。此类模拟文件在 _ mocks _ 目录中定义,在该目录中,文件名被视为模拟模块的名称。
__mocks__/axios.js
1'use strict';
2module.exports = {
3 get: () => {
4 return Promise.resolve({
5 data: [
6 {
7 id: 0,
8 name: 'Wash the dishes'
9 },
10 {
11 id: 1,
12 name: 'Make the bed'
13 }
14 ]
15 });
16 }
17};
如果你要模拟 Node 的某些核心模块(例如 fs 或 path ),则需要在模拟文件中明确调用 jest.mock('moduleName')
Jest 允许我们对函数进行监视:接下来测试是否调用了我们所创建的 get 函数。
app/components/ToDoList.test.js
1import React from 'react';
2import { shallow } from 'enzyme';
3import ToDoList from './ToDoList';
4import axios from 'axios';
5
6jest.mock('axios');
7
8describe('ToDoList component', () => {
9 describe('when rendered', () => {
10 it('should fetch a list of tasks', () => {
11 const getSpy = jest.spyOn(axios, 'get');
12 const toDoListInstance = shallow(
13 <ToDoList/>
14 );
15 expect(getSpy).toBeCalled();
16 });
17 });
18});
通过调用 jest.mock('axios')
,Jest 在的测试和组件中都用我们的模拟代替了 axios。
spyOn 函数返回一个 mock函数。有关其功能的完整列表,请阅读文档。我们的测试检查组件在渲染和运行之后是否从模拟中调用 get函数,并成功执行。
1 PASS app/components/ToDoList/ToDoList.test.js
2 ToDoList component
3 when rendered
4 ✓ should fetch a list of tasks
如果你在多个测试中监视模拟函数,请记住清除每个测试之间的模拟调用,例如通过运行 getSpy.mockClear()
,否则函数调用的次数将在测试之间保持不变。你还可以通过在 package.json 文件中添加以下代码段来使其成为默认行为:
1"jest": {
2 "clearMocks": true
3}
模拟获取 API
另一个常见情况是使用 Fetch API。一个窍门是它是附加到 window 对象的全局函数并对其进行模拟,可以将其附加到 global 对象。首先,让我们创建模拟的 fetch 函数。
__mock__/fetch.js
1export default function() {
2 return Promise.resolve({
3 json: () =>
4 Promise.resolve([
5 {
6 id: 0,
7 name: 'Wash the dishes'
8 },
9 {
10 id: 1,
11 name: 'Make the bed'
12 }
13 ])
14
15 })
16}
然后,将其导入 setupTests.js 文件中。
app/setupTests.js
1import Adapter from 'enzyme-adapter-react-16';
2import { configure } from 'enzyme';
3import fetch from './__mocks__/fetch';
4
5global.fetch = fetch;
6
7configure({adapter: new Adapter()});
注意,你需要在 package.json 中提供指向 setupTests.js 文件的路径——它在本教程的第二部分中进行了介绍。
现在你可以在组件中自由使用 fetch 了。
1componentDidMount() {
2 return fetch(`${apiUrl}/tasks`)
3 .then(tasksResponse => tasksResponse.json())
4 .then(tasksData => {
5 this.setState({
6 tasks: tasksData
7 })
8 })
9 .catch(error => {
10 console.log(error);
11 })
12}
设置监视时,请记住将其设置为 window.fetch
app/components/ToDoList.test.js
1describe('ToDoList component', () => {
2 describe('when rendered', () => {
3 it('should fetch a list of tasks', () => {
4 const fetchSpy = jest.spyOn(window, 'fetch');
5 const toDoListInstance = shallow(
6 <ToDoList/>
7 );
8 expect(fetchSpy).toBeCalled();
9 });
10 });
11});
模拟 React 组件的交互
在之前的文章中,我们提到了阅读组件的状态或属性,但这是在实际与之交互时。为了说明这一点,我们将增加一个把任务添加到 ToDoList 的功能。
app/components/ToDoList.js
1import React, { Component } from 'react';
2import Task from "../Task/Task";
3import axios from 'axios';
4
5class ToDoList extends Component {
6 state = {
7 tasks: [],
8 newTask: '',
9 }
10 componentDidMount() {
11 return axios.get(`${apiUrl}/tasks`)
12 .then(taskResponse => {
13 this.setState({
14 tasks: taskResponse.data
15 })
16 })
17 .catch(error => {
18 console.log(error);
19 })
20 }
21 addATask = () => {
22 const {
23 newTask,
24 tasks
25 } = this.state;
26 if(newTask) {
27 return axios.post(`${apiUrl}/tasks`, {
28 task: newTask
29 })
30 .then(taskResponse => {
31 const newTasksArray = [ ...tasks ];
32 newTasksArray.push(taskResponse.data.task);
33 this.setState({
34 tasks: newTasksArray,
35 newTask: ''
36 })
37 })
38 .catch(error => {
39 console.log(error);
40 })
41 }
42 }
43 handleInputChange = (event) => {
44 this.setState({
45 newTask: event.target.value
46 })
47 }
48 render() {
49 const {
50 newTask
51 } = this.state;
52 return (
53 <div>
54 <h1>ToDoList</h1>
55 <input onChange={this.handleInputChange} value={newTask}/>
56 <button onClick={this.addATask}>Add a task</button>
57 <ul>
58 {
59 this.state.tasks.map(task =>
60 <Task key={task.id} id={task.id} name={task.name}/>
61 )
62 }
63 </ul>
64 </div>
65 )
66 }
67}
68
69export default ToDoList;
如你所见,我们在此处使用了 axios.post。这意味着我们需要扩展 axios 模拟。
__mocks__/axios.js
1'use strict';
2
3let currentId = 2;
4
5module.exports = {
6 get: (url) => {
7 return Promise.resolve({
8 data: [
9 {
10 id: 0,
11 name: 'Wash the dishes'
12 },
13 {
14 id: 1,
15 name: 'Make the bed'
16 }
17 ]
18 });
19 },
20 post: (url, data) {
21 return Promise.resolve({
22 data: {
23 task: {
24 name: data.task,
25 id: currentId++
26 }
27 }
28 });
29 }
30};
我介绍 currentId 变量的原因是想保持ID唯一
首先检查修改输入值是否会改变我们的状态。
app/components/ToDoList.test.js
1import React from 'react';
2import { shallow } from 'enzyme';
3import ToDoList from './ToDoList';
4
5describe('ToDoList component', () => {
6 describe('when the value of its input is changed', () => {
7 it('its state should be changed', () => {
8 const toDoListInstance = shallow(
9 <ToDoList/>
10 );
11
12 const newTask = 'new task name';
13 const taskInput = toDoListInstance.find('input');
14 taskInput.simulate('change', { target: { value: newTask }});
15
16 expect(toDoListInstance.state().newTask).toEqual(newTask);
17 });
18 });
19});
这里的关键是 simulate 函数调用。它是前面提到过的 ShallowWrapper 的功能。我们用它来模拟事件。第一个参数是事件的类型(由于在输入中使用了 onChange,因此在这里应该用 change),第二个参数是模拟事件对象。
为了更进一步,让我们测试一下用户单击按钮后是否从的组件发送了实际的请求。
1import React from 'react';
2import { shallow } from 'enzyme';
3import ToDoList from './ToDoList';
4import axios from 'axios';
5
6jest.mock('axios');
7
8describe('ToDoList component', () => {
9 describe('when the button is clicked with the input filled out', () => {
10 it('a post request should be made', () => {
11 const toDoListInstance = shallow(
12 <ToDoList/>
13 );
14 const postSpy = jest.spyOn(axios, 'post');
15
16 const newTask = 'new task name';
17 const taskInput = toDoListInstance.find('input');
18 taskInput.simulate('change', { target: { value: newTask }});
19
20 const button = toDoListInstance.find('button');
21 button.simulate('click');
22
23 expect(postSpy).toBeCalled();
24 });
25 });
26});
测试通过了!
现在事情会变得有些棘手。我们将要测试状态是否能够随着的新任务而更新。有趣的是请求是异步的。
1import React from 'react';
2import { shallow } from 'enzyme';
3import ToDoList from './ToDoList';
4import axios from 'axios';
5
6jest.mock('axios');
7
8describe('ToDoList component', () => {
9 describe('when the button is clicked with the input filled out, the new task should be added to the state', () => {
10 it('a post request should be made', () => {
11 const toDoListInstance = shallow(
12 <ToDoList/>
13 );
14 const postSpy = jest.spyOn(axios, 'post');
15
16 const newTask = 'new task name';
17 const taskInput = toDoListInstance.find('input');
18 taskInput.simulate('change', { target: { value: newTask }});
19
20 const button = toDoListInstance.find('button');
21 button.simulate('click');
22
23 const postPromise = postSpy.mock.results.pop().value;
24
25 return postPromise.then((postResponse) => {
26 const currentState = toDoListInstance.state();
27 expect(currentState.tasks.includes((postResponse.data.task))).toBe(true);
28 })
29 });
30 });
31});
如你所见,postSpy.mock.results 是 post 所有结果的数组函数,通过它我们可以得到返回的 promise:在 value 属性中可用。
从测试中返回 promise 是能够确保 Jest 等待其解决的一种方法。
总结
在本文中,我们介绍了模拟模块,并将其用于伪造 API 调用。由于没有发出实际的请求要求,我们的测试可以更可靠、更快。除此之外,我们还在整个 React 组件中模拟了事件,并检查了它是否产生了预期的结果,例如组件的请求或状态变化,并且了解了监视的概念。
-
4. JavaScript测试教程–part 4:模拟 API 调用和模拟 React 组件交互
原文:https://wanago.io/2018/09/17/javascript-testing-tutorial-part-four-mocking-api-calls-and-simulation-react-components-interactions/
下面夹杂一些私货:也许你和高薪之间只差这一张图
2019年京程一灯课程体系上新,这是我们第一次将全部课程列表对外开放。
愿你有个好前程,愿你月薪30K。我们是认真的 !
在公众号内回复“体系”查看高清大图
长按二维码,加大鹏老师微信好友
拉你加入前端技术交流群
唠一唠怎样才能拿高薪
往期精选
小手一抖,资料全有。长按二维码关注前端先锋,阅读更多技术文章和业界动态。
文章评论