[React] Ensure all React useEffect Effects Run Synchronously in Tests with react-testing-library
Thanks to react-testing-library our tests are free of implementation details, so when we refactor components to hooks we generally don't need to make any changes to our tests. However, useEffectis slightly different from componentDidMount in that it's actually executed asynchronously after the render has taken place. So all of our query tests which relied on the HTTP requests being sent immediately after render are failing. Let's use the flushEffects utility from react-testing-library to ensure that the pending effect callbacks are run before we make assertions.
Component code:
import {useContext, useReducer, useEffect} from 'react'
import * as GitHub from '../../../github-client'
function Query ({query, variables, children, normalize = data => data}) {
const client = useContext(GitHub.Context)
const defaultState = {loaded: false, fetching: false, data: null, error: null}
const [state, setState] = useReducer(
(state, newState) => ({...state, ...newState}),
defaultState)
useEffect(() => {
setState({fetching: true})
client
.request(query, variables)
.then(res =>
setState({
data: normalize(res),
error: null,
loaded: true,
fetching: false,
}),
)
.catch(error =>
setState({
error,
data: null,
loaded: false,
fetching: false,
}),
)
}, [query, variables]) // trigger the effects when 'query' or 'variables' changes
return children(state)
}
export default Query
Test Code:
import {render as rtlRender, wait, flushEffects} from 'react-testing-library'
import React from 'react'
import {render as rtlRender, wait, flushEffects} from 'react-testing-library'
import * as GitHubClient from '../../../../github-client'
import Query from '../query' const fakeResponse = {fakeData: {}}
const fakeClient = {request: jest.fn(() => Promise.resolve(fakeResponse))} beforeEach(() => {
fakeClient.request.mockClear()
}) function renderQuery({
client = fakeClient,
children = jest.fn(() => null),
query = '',
variables = {},
normalize,
...options
} = {}) {
const props = {query, variables, children, normalize}
const utils = rtlRender(
<GitHubClient.Provider client={client}>
<Query {...props} />
</GitHubClient.Provider>,
options,
)
return {
...utils,
rerender: options =>
renderQuery({
container: utils.container,
children,
query,
variables,
normalize,
...options,
}),
client,
query,
variables,
children,
}
} test('query makes requests to the client on mount', async () => {
const {children, client, variables, query} = renderQuery()
flushEffects();
expect(children).toHaveBeenCalledTimes()
expect(children).toHaveBeenCalledWith({
data: null,
error: null,
fetching: true,
loaded: false,
})
expect(client.request).toHaveBeenCalledTimes()
expect(client.request).toHaveBeenCalledWith(query, variables) children.mockClear()
await wait() expect(children).toHaveBeenCalledTimes()
expect(children).toHaveBeenCalledWith({
data: fakeResponse,
error: null,
fetching: false,
loaded: true,
})
}) test('does not request if rerendered and nothing changed', async () => {
const {children, client, rerender} = renderQuery()
flushEffects();
await wait()
children.mockClear()
client.request.mockClear()
rerender()
flushEffects();
await wait()
expect(client.request).toHaveBeenCalledTimes()
expect(children).toHaveBeenCalledTimes() // does still re-render children.
}) test('makes request if rerendered with new variables', async () => {
const {client, query, rerender} = renderQuery({
variables: {username: 'fred'},
})
flushEffects();
await wait()
client.request.mockClear()
const newVariables = {username: 'george'}
rerender({variables: newVariables})
flushEffects();
await wait()
expect(client.request).toHaveBeenCalledTimes()
expect(client.request).toHaveBeenCalledWith(query, newVariables)
}) test('makes request if rerendered with new query', async () => {
const {client, variables, rerender} = renderQuery({
query: `query neat() {}`,
})
flushEffects();
await wait()
client.request.mockClear()
const newQuery = `query nice() {}`
rerender({query: newQuery})
flushEffects();
await wait()
expect(client.request).toHaveBeenCalledTimes()
expect(client.request).toHaveBeenCalledWith(newQuery, variables)
}) test('normalize allows modifying data', async () => {
const normalize = data => ({normalizedData: data})
const {children} = renderQuery({normalize})
flushEffects();
await wait()
expect(children).toHaveBeenCalledWith({
data: {normalizedData: fakeResponse},
error: null,
fetching: false,
loaded: true,
})
})
[React] Ensure all React useEffect Effects Run Synchronously in Tests with react-testing-library的更多相关文章
- React Hook:使用 useEffect
React Hook:使用 useEffect 一.描述 二.需要清理的副作用 1.在 class 组件中 2.使用 effect Hook 的示例 1.useEffect 做了什么? 2.为什么在组 ...
- React Hooks --- useState 和 useEffect
首先要说的一点是React Hooks 都是函数,使用React Hooks,就是调用函数,只不过不同的Hooks(函数)有不同的功能而已.其次,React Hooks只能在函数组件中使用,函数组件也 ...
- React中useLayoutEffect和useEffect的区别
重点: 1.二者函数签名相同,调用方式是一致的 2. 怎么简单进行选择: 无脑选择useEffect,除非运行效果和你预期的不一致再试试useLayoutEffect 区别详解:useEffect是异 ...
- React Native Android原生模块开发实战|教程|心得|怎样创建React Native Android原生模块
尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691503) 告诉大家一个好消息. ...
- 如何使用TDD和React Testing Library构建健壮的React应用程序
如何使用TDD和React Testing Library构建健壮的React应用程序 当我开始学习React时,我努力的一件事就是以一种既有用又直观的方式来测试我的web应用程序. 每次我想测试它时 ...
- [React] Safely setState on a Mounted React Component through the useEffect Hook
In the class version of this component, we had a method called safeSetState which would check whethe ...
- react创建项目后运行npm run eject命令将配置文件暴露出来时报错解决方法
最近在用create-react-app创建项目,因要配置各种组件,比如babel,antd等, 需要运行npm run eject命令把项目的配置文件暴露出来,但是还是一如既然碰到报错,因为是在本地 ...
- react+ant design 项目执行yarn run eject 命令后无法启动项目
如何将内建配置全部暴露? 使用create-react-app结合antd搭建的项目中,项目目录没有该项目所有的内建配置, 1.执行yarn run eject 执行该命令后,运行项目yarn sta ...
- React 16 源码瞎几把解读 【二】 react组件的解析过程
一.一个真正的react组件编译后长啥样? 我们瞎几把解读了react 虚拟dom对象是怎么生成的,生成了一个什么样的解构.一个react组件不光由若干个这些嵌套的虚拟dom对象组成,还包括各种生命周 ...
随机推荐
- 洛谷P2151 [SDOI2009] HH去散步 [矩阵加速]
题目传送门 HH去散步 题目描述 HH有个一成不变的习惯,喜欢饭后百步走.所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离. 但是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走 ...
- Python之路【第一篇】:介绍、基本语法、流程控制
一.python 简介 python 特点 Python是一种计算机程序设计语言.你可能已经听说过很多种流行的编程语言,比如非常难学的C语言,非常流行的Java语言,适合初学者的Basic语言,适合网 ...
- Android中xml tool属性
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0309/2567.html 我是搬运工.. 总结而言tool属性就是为了在IDE ...
- Unity 2D游戏开发教程之摄像头追踪功能
Unity 2D游戏开发教程之摄像头追踪功能 上一章,我们创建了一个简单的2D游戏.此游戏中的精灵有3个状态:idle.left和right.这看起来确实很酷!但是仅有的3个状态却限制了精灵的能力,以 ...
- XamarinForms教程构建XamarinForms开发环境
构建XamarinForms开发环境 所谓Xamarin.Forms的开发环境,就是指在基本硬件和数字软件的基础上,为支持系统软件和应用软件的工程化开发和维护而使用的一组软件,简称SDE.对于任何的程 ...
- React Native 系列(五)
前言 本系列是基于React Native版本号0.44.3写的.任何一款 App 都有界面之间数据传递的这个步骤的,那么在RN中,组件间是怎么传值的呢?这篇文章将介绍到顺传.逆传已经通过通知传值. ...
- Python 2.7.x 和 3.x 版本的语法区别
<__future__模块> Python 3.x引入了一些与Python 2不兼容的关键字和特性,在Python 2中,可以通过内置的__future__模块导入这些新内容.如果你希望在 ...
- [CC-ANUGCD]Maximum number, GCD condition
[CC-ANUGCD]Maximum number, GCD condition 题目大意: 一个长度为\(n(n\le10^5)\)的数列\(A(A_i\le10^5)\),\(m(m\le10^5 ...
- [SimpleOJ233]a xor b
题目大意: 给你一个数列,求所有区间最大值和次大只异或的最大值. 思路: 很容易想到一个O(n^2)的暴力. O(n)的单调栈做法似乎也很好想,不过考场上没想出来. 对于数列上的某一个数,我们维护在它 ...
- [转]Android适配器之ArrayAdapter、SimpleAdapter和BaseAdapter的简单用法与有用代码片段
收藏ArrayAdapter.SimpleAdapter和BaseAdapter的一些简短代码片段,希望用时方便想起其用法. 1.ArrayAdapter 只可以简单的显示一行文本 代码片段: A ...