react dispatch_React测试的那些事(三) React Hook 测试实例

v2-888af5ed0ebd34d023ff4d36da55176a_1440w.jpg?source=172ae18b

useReducer

测试 useReducer 首先需要在组件中用 actions 和 reducers ,代码如下。

Reducer

import * as ACTIONS from './actions'export const initialState = {stateprop1: false,
}export const Reducer1 = (state = initialState, action) => {switch(action.type) {case "SUCCESS":return {...state,stateprop1: true,}case "FAILURE":return {...state,stateprop1: false,}default:return state}
}

actions

export const SUCCESS = {type: 'SUCCESS'
}export const FAILURE = {type: 'FAILURE'
}

我们先写个简单的,只用action,不用action creators 代码如下:

import React, { useReducer } from 'react';
import * as ACTIONS from '../store/actions'
import * as Reducer from '../store/reducer'const TestHookReducer = () => {const [reducerState, dispatch] = useReducer(Reducer.Reducer1, Reducer.initialState)const dispatchActionSuccess = () => {dispatch(ACTIONS.SUCCESS)}const dispatchActionFailure = () => {dispatch(ACTIONS.FAILURE)}return (<div><div>{reducerState.stateprop1? <p>stateprop1 is true</p>: <p>stateprop1 is false</p>}</div><button onClick={dispatchActionSuccess}>Dispatch Success</button></div>)
}export default TestHookReducer;

这就是一个简单的组件,通过dispatching 名为SUCCESS 的动作,把 stateprop1从 false 变成 true 。这是一个超基本的测试,保证initial state是我们想要的结果。

你可能想说,测试reducer就是测试实现的具体细节,不建议这样做的呀?但在实践中发现这种测试还是很必要的,它也算作一种单元测试。

这个简单的例子里面测试reducers看起来不是什么大事。当状态更复杂的情况不进行测试会产生很多问题。所以请务必对actionsreducers进行测试。

~useContext~

下面我们设想另一个场景,一个子组件能够更新父组件的上下文环境的state。听起来有点绕,实际上很简单。

首先初始化一个Context对象

import React from 'react';const Context = React.createContext()export default Context

父组件中提供Context.provider。传递给Provider的值是 App.js组件中setState函数 和state

import React, { useState } from 'react';
import TestHookContext from './components/react-testing-lib/test_hook_context';import Context from './components/store/context';const App = () => {const [state, setState] = useState("Some Text")const changeText = () => {setState("Some Other Text")}return (<div className="App"><h1> Basic Hook useContext</h1><Context.Provider value={{changeTextProp: changeText,stateProp: state}} ><TestHookContext /></Context.Provider></div>);
}export default App;

子组件非常简单:展示在父组件中初始化的文字,当点击按钮时执行setState函数。

import React, { useContext } from 'react';import Context from '../store/context';const TestHookContext = () => {const context = useContext(Context)return (<div><button onClick={context.changeTextProp}>Change Text</button><p>{context.stateProp}</p></div>)
}export default TestHookContext;

父组件中状态进行了初始化和改变。我们只是用setState函数将状态值传递给子组件。所以我们如下进行测试

import React from 'react';
import ReactDOM from 'react-dom';
import TestHookContext from '../test_hook_context.js';
import {act, render, fireEvent, cleanup} from '@testing-library/react';
import App from '../../../App'import Context from '../../store/context';afterEach(cleanup)it('Context value is updated by child component', () => {const { container, getByText } = render(<App><Context.Provider><TestHookContext /></Context.Provider></App>);expect(getByText(/Some/i).textContent).toBe("Some Text")fireEvent.click(getByText("Change Text"))expect(getByText(/Some/i).textContent).toBe("Some Other Text")
})

虽然我们在render函数中写了<Context.Provider/><TestHookContext />,但实际上并没必要。写是为了容易理解代码,不写呢程序还是会运行

const { container, getByText } = render(<App/>) 

~一点思考~

让我们来回想下整个过程。所有的context state包含在父组件中,所以我们实际上测试的就是父组件,只是看起来像在用 useContext 测试着子组件而已。由于mount/render能渲染子组件(shallow不会渲染子组件),所以 <Context.Provider/><TestHookContext />这俩子组件被自动渲染出来了。

表单中的受控组件

受控组件代表着这个表单的state并没有掌握在组件手里而在React的状态中。每个按键都把输入的内容通过 onChange 保存在了React状态里。

测试这样的组件会比之前的复杂一些。

先看一个非常基本表单的组件

import React, { useState } from 'react';const HooksForm1 = () => {const [valueChange, setValueChange] = useState('')const [valueSubmit, setValueSubmit] = useState('')const handleChange = (event) => (setValueChange(event.target.value));const handleSubmit = (event) => {event.preventDefault();setValueSubmit(event.target.text1.value)};return (<div><h1> React Hooks Form </h1><form data-testid="form" onSubmit={handleSubmit}><label htmlFor="text1">Input Text:</label><input id="text1" onChange={handleChange} type="text" /><button type="submit">Submit</button></form><h3>React State:</h3><p>Change: {valueChange}</p><p>Submit Value: {valueSubmit}</p><br /></div>)
}export default HooksForm1;

组件很简单,包含form中基本的change、submit操作,form的data-testid=form可以作为查询的ID值。

测试

import React from 'react';
import ReactDOM from 'react-dom';
import HooksForm1 from '../test_hook_form.js';
import {render, fireEvent, cleanup} from '@testing-library/react';afterEach(cleanup)//testing a controlled component form.
it('Inputing text updates the state', () => {const { getByText, getByLabelText } = render(<HooksForm1 />);expect(getByText(/Change/i).textContent).toBe("Change: ")fireEvent.change(getByLabelText("Input Text:"), {target: {value: 'Text' } } )expect(getByText(/Change/i).textContent).not.toBe("Change: ")})it('submiting a form works correctly', () => {const { getByTestId, getByText } = render(<HooksForm1 />);expect(getByText(/Submit Value/i).textContent).toBe("Submit Value: ")fireEvent.submit(getByTestId("form"), {target: {text1: {value: 'Text' } } })expect(getByText(/Submit Value/i).textContent).not.toBe("Submit Value: ")})
  • 由于input元素还没有输入值,我们用getByLabelText()函数找到它。这也符合我们的测试原则,因为用户再输入值之前也看的label呀。
  • 我们用.change()代替了.click()事件,也可以用{target: {value: "Text"}}的方式传递假数据。
  • 表单用event.target.value取值,这就是我们模拟事件时传参的对象。
  • 由于我们并不确定用户输入的是什么内容,可以用.not确保渲染的内容确实变了。
  • 我们可以用相似方法测试表单的提交。不同之处为 .submit()传这串信息{target: {text1: {value: 'Text'}}} (input元素的id是text1)
  • 在这里用data-testid="form"匹配到我们的form元素,因为这是最优的办法了。

以上,介绍了获取用户提交表单的数据的方法。是不是和之前的例子相差不大?如果没问题的话,接下来看点更复杂的吧。

useEffect 和 API请求

接下来我们看看如何测试useEffect hook 和 API请求(axios) ,与之前的都不太一样。

先假设有一个url从 根组件传递到子组件

...<TestAxios url='https://jsonplaceholder.typicode.com/posts/1' />... 

简单的发API请求并把结果保存在本地state的组件

import React, { useState, useEffect } from 'react';
import axios from 'axios';const TestAxios = (props) => {const [state, setState] = useState()useEffect(() => {axios.get(props.url).then(res => setState(res.data))}, [])return (<div><h1> Axios Test </h1>{state? <p data-testid="title">{state.title}</p>: <p>...Loading</p>}</div>)
}export default TestAxios;
  • 标题的placeholder显示的内容是从一个三目运算符中得来的。
  • 本例仍需用 data-testid属性 ,虽然用户看不到也接触不到它,但在API返回数据之前不知道是什么值,所以靠此属性来匹配到元素。

这里我们用mock数据(Mock是在测试中常用的模拟方法,比如用mock API 模拟真实的请求)因为用真实的数据进行测试的话,拖慢了测试的速度,有时接口会有意外的错误,测试数据会弄乱数据库等问题。

~引入依赖~

import React from 'react';
import ReactDOM from 'react-dom';
import TestAxios from '../test_axios.js';
import {act, render, fireEvent, cleanup, waitForElement} from '@testing-library/react';import axiosMock from "axios";

有句之前没介绍过的引入 import axiosMock from "axios";它不是说从axios库中引入axiosMock,而是mock了axios这个库。

~mock~

是不是很奇怪,它怎么做到的?它用到了Jest提供的模拟功能。

首先我们创建一个__mocks__文件夹,位置与__test__相邻。

__mocks__文件夹中创建一个 axios.js文件,它就是我们伪造的axios库。在我们伪造的axios库中加入jest mock 函数。嗯?这是什么函数?在jest环境中无需实现具体的请求逻辑,直接用这个模拟函数返回数据即可。喏~ 看个例子

export default {get: jest.fn(() => Promise.resolve({ data: {} }) )
};
  • 此处简单的示例中,伪造的get函数就是一个JS对象;
  • get就是key值,value就是 mock 函数
  • 就像一个 axiosAPI请求,我们得到了一个promise
  • 这个例子中没有填写任何返回数据,接下来我们会加上返回值

~加入mock返回值的测试~

//imports
...afterEach(cleanup)it('Async axios request works', async () => {axiosMock.get.mockResolvedValue({data: { title: 'some title' } })const url = 'https://jsonplaceholder.typicode.com/posts/1'const { getByText, getByTestId, rerender } = render(<TestAxios url={url} />);expect(getByText(/...Loading/i).textContent).toBe("...Loading")const resolvedEl = await waitForElement(() => getByTestId("title"));expect((resolvedEl).textContent).toBe("some title")expect(axiosMock.get).toHaveBeenCalledTimes(1);expect(axiosMock.get).toHaveBeenCalledWith(url);})
  • 我们做的第一件事,调用了伪造的 axios get request ,伪造请求结果我们用的是jest提供的方法mockResolvedValue ,这个函数做的和它的函数名一样,它像axios那样 resolves一个promise
  • mockResolvedValue需要在render之前进行调用,否则test不会生效。因为它是我们伪造的 axios,当执行import axios from 'axios'; 时,会导入我们伪造的axios,并把组件中用到的axios全部替换掉。
  • 接下来,在promise返回前,一直处于加载状态,UI上出现...Loading
  • waitForElement()函数我们之前都没见过,它会等到promise返回结果后才跳到下一个断言。
  • awaitasync 他们的用法与正常的非测试场景是一样的。
  • 当解析出DOM后,UI会出现我们伪造的mock返回值“some title”
  • 接下来我们要确保请求只调用了一次和url的正确性(虽然没用到这个URL我们也要这么测试一下)

以上就是如何对axios的请求进行测试,下面一章我们会讲到如何用cypress进行e to e测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/298510.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Blazor 组件库开发指南

翻译自 Waqas Anwar 2021年5月21日的文章 《A Developer’s Guide To Blazor Component Libraries》 [1]Blazor 的核心是组件&#xff0c;我们创建不同类型的组件并在整个项目中重用它们。没有人想重复造轮子&#xff0c;因此创建一个可重用的 Blazor 组件库始终是一个好主意&a…

今年大学毕业照画风太清奇.....我忍不住笑出了声!

全世界只有3.14 % 的人关注了爆炸吧知识转眼又到毕业季&#xff0c;今年毕业季因疫情有些不一样。PS云毕业照、云答辩、云面试&#xff0c;不少毕业生调侃自己是“云毕业”。最近不少大学生在网上哀嚎&#xff0c;原因是学校要他们上交照片&#xff0c;以便制作毕业照&#xff…

vc 控制台添加托盘显示_开源:ESP8266读DHT11温湿度,小程序实时显示

第一、原理讲解原理简述&#xff1a;利用发布订阅模式。第一步&#xff0c;先读取DHT11温湿度数值&#xff0c;第二步&#xff0c;新建主题&#xff0c;然后esp8266往这个主题发送消息&#xff0c;第三步。小程序通过http API接口获取数据。小程序控制原理&#xff1a;esp8266订…

python if elif else_python:通讯录(字典+while+if/else)

Hello,World.我是很久很久很久都没有更新的土土[看]但是即便如此呢我这个人也不喜欢拖拖拉拉的东扯西扯一上来咱们就来讲讲正题勿怪[笑哭]1.使用字典构建通讯录mydic{}//定义一个字典2.使用whlie循环判断是否继续进行while True://循环开始break//循环结束3.使用if/elif/else语…

有BUG!!!慎用default文本

前言下面这段代码&#xff0c;你能发现什么问题吗&#xff1f;public static int? Test1(string str) {return str switch{"A" > 1,"B" > 2,"C" > 3,_ > default,}; } public static int? Test2(string str) {switch (str){case &…

老师看完都吐血的五道题

全世界只有3.14 % 的人关注了 爆炸吧知识 老师看完都要吐血的五道题哈哈哈哈哈虽然小编觉得最后一答完全没问题哈哈哈图片来源于网络&#xff0c;如有侵权请联系删除。你点的每个在看&#xff0c;我都认真当成了喜欢

JDK5.0新特性--可变参数

2019独角兽企业重金招聘Python工程师标准>>> /*** 可变参数*/ public class VariableParamater {/*** 统计2个或多个数的和*/public static void main(String[] args) {//计算2个数的和sum(10,15);/** 问题&#xff1a;如果要计算3个数、4个数、5个数或n个数的和&am…

欧几里得距离网络_使用Tensorflow对象检测模型和OpenCV的社交距离和遮罩检测器...

将深度学习和计算机视觉相结合的隔离项目社会距离化这个术语已席卷全球&#xff0c;正在改变着我们的生活方式。社交距离也称为“物理距离”&#xff0c;是指在您自己与其他并非来自家庭的人之间保持安全的空间。随着该国开始在激增的COVID-19案件中发挥作用&#xff0c;保持社…

python 实现点击右键用某个程序打开功能_工欲善其事,必先利其器(1)——Python开发环境安装与配置...

Life Is Short, You Need Python.近年来随着Python的火爆&#xff0c;很多人想要学习并使用Python&#xff0c;本文将详细介绍一套Python开发环境的安装与配置&#xff0c;相比Python自带的IDEL&#xff0c;在此环境下将极大的提高开发效率。下面就让我们一步步的开始安装&…

5分钟盗走你的隐私照片,这个全球性漏洞到底有多可怕

全世界只有3.14 % 的人关注了爆炸吧知识转自&#xff1a;好机友ID&#xff1a;goodjiyou这个时代&#xff0c;大家对电脑出现漏洞&#xff0c;可能已经习以为常。但如果机哥告诉大家&#xff0c;这个漏洞能够在 5 分钟内&#xff0c;破解并盗取你所有加密文件&#xff0c;而且还…

我的世界命令计算机,我的世界有哪些指令?电脑版指令大全

在我的世界游戏中玩家可以依靠指令做很多现实生活中都可以做的事情&#xff0c;甚至有些现实中做不到的事情&#xff0c;比如上天堂&#xff0c;所以指令对于每个我的世界的玩家来说都非常重要&#xff0c;今天小编给大家介绍我的世界指令大全&#xff1a;我的世界指令大全&…

道德经和译文_老子《道德经》第九章原文、注释、译文、导读及解析(收藏版)...

《道德经》被誉为“万经之王”&#xff0c;内容涵盖哲学、伦理学、政治学、军事学等诸多学科&#xff0c;曾被后人尊奉为治国、齐家、修身、为学的宝典。它对我国的哲学、科学、政治、宗教等都产生了深远的影响&#xff0c;体现了古人的一种世界观和人生观。《道德经》作为道教…

StackOverflow程序员推荐:每个程序员都应读的30本书

“如果能时光倒流&#xff0c;回到过去&#xff0c;作为一个开发人员&#xff0c;你可以告诉自己在职业生涯初期应该读一本&#xff0c;你会选择哪本书呢&#xff1f;我希望这个书单列表内容丰富&#xff0c;可以涵盖很多东西。” 很多程序员响应&#xff0c;他们在推荐时也写下…

超震撼!你没见过的24张震撼照片

全世界只有3.14 % 的人关注了爆炸吧知识话不多说&#xff0c;这些照片&#xff0c;每张都很有价值&#xff1a;艾菲尔铁塔&#xff0c;从底部往上看360度的彩虹高科技停车&#xff08;大众沃尔夫斯堡汽车厂&#xff09;被整理过的蜂窝沙特阿拉伯的农田两棵树共享一个枝干两位接…

使用 baget 搭建 nuget 私有服务

现在几乎所有语言都提供包管理工具&#xff0c;比如 JavaScript 的 npm &#xff0c;Java 的 Maven &#xff0c;Dart 的 pub 。.Net 程序当然是 NuGet 。NuGet 也出现很多年了&#xff0c;奇怪的是居然还有很多人不知道。现在软件结构越来越复杂&#xff0c;在多个项目中往往需…

xp计算机dns怎么设置,XP系统dns怎么设置?XP系统dns的设置方法

在使用XP系统浏览网页时&#xff0c;大家有没碰到网页打开很慢&#xff0c;而网速检测又显示正常的情况。为什么会出现这样的情况呢&#xff1f;一方面可能是你的电脑中毒了&#xff0c;另一方面可能是你的dns选择的不够好。那么dns要怎么设置才最好呢&#xff1f;下面小编以XP…

线程打印_面试题:用程序实现两个线程交替打印 0~100 的奇偶数

作者&#xff1a;dadiyang来源&#xff1a;https://blog.csdn.net/dadiyang/article/details/88315124面试场景面试官&#xff1a;Java多线程了解吗&#xff1f;你给我写一下&#xff0c;起两个线程交替打印0~100的奇偶数。小黄&#xff1a;啊&#xff1f;面试官&#xff1a;就…

asp.net添加删除表格_如何用openpyxl自动化编写Excel电子表格

有很多不同的东西你可以写到电子表格&#xff0c;从简单的文本或数字值到复杂的公式&#xff0c;图表&#xff0c;甚至图像。创建一个简单的电子表格之前&#xff0c;大家看到了一个非常快速的例子&#xff0c;就是如何将 "Hello world!"写进电子表格中&#xff0c;所…

纯css3实现的鼠标悬停动画按钮

今天给大家带来一款纯css3实现的鼠标悬停动画按钮。这款按钮鼠标经过前以正方形的形式&#xff0c;当鼠标经过的时候以动画的形式变成圆形。效果图如下&#xff1a; 在线预览 源码下载 实现的代码。 html代码&#xff1a; <div><span></span></div> …

如何交到一个女朋友?

1 北京的路&#xff0c;上海的路和重庆的路2 这兔子是吃弹簧长大的吧&#xff01;3 猫和老鼠原来是真的4 把水凝胶珠投入有颜色的水&#xff0c;过一会儿之后...5 为了同学得分&#xff0c;老师有多努力你知道吗&#xff1f;6 不同年龄段爱用的表情7 来自一个幼儿园小男生的教学…