侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

模拟 API 调用和模拟 React 组件交互

前端  /  管理员 发布于 6年前   245

今天,我们进一步测试 react 组件。它涉及模拟组件交互和模拟 API 调用。你将学到两种方法,开始吧!


模拟

对于我们的程序来说,从 API 获取一些数据是很常见的。但是它可能由于各种原因而失败,例如 API 被关闭。我们希望测试可靠且独立,并确保可以模拟某些模块。我们把 ToDoList 组件修改为智能组件。

app/components/ToDoList.component.js
import react, { Component } from 'react';import Task from "../Task/Task";import axios from 'axios'; 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 (      <div>        <h1>ToDoList</h1>        <ul>          {            this.state.tasks.map(task =>              <Task key={task.id} id={task.id} name={task.name}/>            )          }        </ul>      </div>    )  }} export default ToDoList;

它使用 axios 提取数据,所以需要模拟该模块,因为我们不希望发出实际的请求。此类模拟文件在 mocks 目录中定义,在该目录中,文件名被视为模拟模块的名称。

__mocks__/axios.js
'use strict';module.exports = {  get: () => {    return Promise.resolve({      data: [        {          id: 0,          name: 'Wash the dishes'        },        {          id: 1,          name: 'Make the bed'        }      ]    });  }};
如果你要模拟 Node 的某些核心模块(例如 fs 或 path ),则需要在模拟文件中明确调用 jest.mock('moduleName')

Jest 允许我们对函数进行监视:接下来测试是否调用了我们所创建的 get 函数。

app/components/ToDoList.test.js
import React from 'react';import { shallow } from 'enzyme';import ToDoList from './ToDoList';import axios from 'axios'; jest.mock('axios'); 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.mock('axios'),Jest 在的测试和组件中都用我们的模拟代替了 axios。

spyOn 函数返回一个 mock函数。有关其功能的完整列表,请阅读文档。我们的测试检查组件在渲染和运行之后是否从模拟中调用 get函数,并成功执行。

 PASS  app/components/ToDoList/ToDoList.test.js  ToDoList component    when rendered      ✓ should fetch a list of tasks

如果你在多个测试中监视模拟函数,请记住清除每个测试之间的模拟调用,例如通过运行 getSpy.mockClear(),否则函数调用的次数将在测试之间保持不变。你还可以通过在 package.json 文件中添加以下代码段来使其成为默认行为:

"jest": {  "clearMocks": true}


模拟获取 API

另一个常见情况是使用 Fetch API。一个窍门是它是附加到 window 对象的全局函数并对其进行模拟,可以将其附加到 global 对象。首先,让我们创建模拟的 fetch 函数。

__mock__/fetch.js
export default function() {  return Promise.resolve({    json: () =>      Promise.resolve([        {          id: 0,          name: 'Wash the dishes'        },        {          id: 1,          name: 'Make the bed'        }      ])   })}

然后,将其导入 setupTests.js 文件中。

app/setupTests.js
import Adapter from 'enzyme-adapter-react-16';import { configure } from 'enzyme';import fetch from './__mocks__/fetch'; global.fetch = fetch; configure({adapter: new Adapter()});
注意,你需要在 package.json 中提供指向 setupTests.js 文件的路径——它在本教程的第二部分中进行了介绍。

现在你可以在组件中自由使用 fetch 了。

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

设置监视时,请记住将其设置为 window.fetch

app/components/ToDoList.test.js
describe('ToDoList component', () => {  describe('when rendered', () => {    it('should fetch a list of tasks', () => {      const fetchSpy = jest.spyOn(window, 'fetch');      const toDoListInstance = shallow(        <ToDoList/>      );      expect(fetchSpy).toBeCalled();    });  });});


模拟 React 组件的交互

在之前的文章中,我们提到了阅读组件的状态或属性,但这是在实际与之交互时。为了说明这一点,我们将增加一个把任务添加到 ToDoList 的功能。

app/components/ToDoList.js
import React, { Component } from 'react';import Task from "../Task/Task";import axios from 'axios'; class ToDoList extends Component {  state = {    tasks: [],    newTask: '',  }  componentDidMount() {    return axios.get(`${apiUrl}/tasks`)      .then(taskResponse => {        this.setState({          tasks: taskResponse.data        })      })      .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;

如你所见,我们在此处使用了 axios.post。这意味着我们需要扩展 axios 模拟。

__mocks__/axios.js
'use strict'; let currentId = 2; module.exports = {  get: (url) =&gt; {    return Promise.resolve({      data: [        {          id: 0,          name: 'Wash the dishes'        },        {          id: 1,          name: 'Make the bed'        }      ]    });  },  post: (url, data) {    return Promise.resolve({      data: {        task: {          name: data.task,          id: currentId++        }      }    });  }};
我介绍 currentId 变量的原因是想保持ID唯一

首先检查修改输入值是否会改变我们的状态。

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 函数调用。它是前面提到过的 ShallowWrapper 的功能。我们用它来模拟事件。第一个参数是事件的类型(由于在输入中使用了 onChange,因此在这里应该用 change),第二个参数是模拟事件对象。

为了更进一步,让我们测试一下用户单击按钮后是否从的组件发送了实际的请求。

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();    });  });});

测试通过了!

现在事情会变得有些棘手。我们将要测试状态是否能够随着的新任务而更新。有趣的是请求是异步的。

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 是能够确保 Jest 等待其解决的一种方法。


总结

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

原文:https://wanago.io/2018/09/17/JavaScript-testing-tutorial-part-four-mocking-api-calls-and-simulation-react-components-interactions/



  • 上一条:
    vue2实现带地区编号和名称的省市县三级联动效果
    下一条:
    在Vue中使用Highcharts
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 使用 Alpine.js 排序插件对元素进行排序(0个评论)
    • 在js中使用jszip + file-saver实现批量下载OSS文件功能示例(0个评论)
    • 在vue中实现父页面按钮显示子组件中的el-dialog效果(0个评论)
    • 使用mock-server实现模拟接口对接流程步骤(0个评论)
    • vue项目打包程序实现把项目打包成一个exe可执行程序(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-03
    • 2018-04
    • 2018-05
    • 2018-06
    • 2018-09
    • 2018-11
    • 2018-12
    • 2019-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-04
    • 2021-05
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客