[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对象组成,还包括各种生命周 ...
随机推荐
- [C#] 实现可设置超时的 Task
前言 如何实现支持超时的 Task ? 关键点: Task.WhenAny 实现 一个扩展方法就可以搞定. // 有返回值 public static async Task<TResult> ...
- 光线求交-面、三角形、球 (Ray intersection)
光线求交 光线定义:position \(a(t)\) = \(o\) + \(t\vec{d}\); 球定义: center p, radius r; 平面定义:normal \(\vec{n}\) ...
- [BZOJ2402]陶陶的难题II(树链剖分+线段树维护凸包+分数规划)
陶陶的难题II 时间限制:40s 空间限制:128MB 题目描述 输入格式 第一行包含一个正整数N,表示树中结点的个数. 第二行包含N个正实数,第i个数表示xi (1<=xi<= ...
- Python字典树实现
class Trie: # word_end = -1 def __init__(self): """ Initialize your data structure he ...
- 斐波那契数列(Java实现)
描述 一个斐波那契序列,F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2) (n>=2),根据n的值,计算斐波那契数F(n),其中0≤n≤1000. 输入 输入 ...
- java高并发程序设计模式-并发级别:阻塞、无障碍、无锁、无等待【转载】
一般认为并发可以分为阻塞与非阻塞,对于非阻塞可以进一步细分为无障碍.无锁.无等待,下面就对这几个并发级别,作一些简单的介绍. 1.阻塞 阻塞是指一个线程进入临界区后,其它线程就必须在临界区外等待,待进 ...
- 【BZOJ】3697: 采药人的路径
3697: 采药人的路径 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1718 Solved: 602[Submit][Status][Discu ...
- bzoj 1911: [Apio2010]特别行动队 -- 斜率优化
1911: [Apio2010]特别行动队 Time Limit: 4 Sec Memory Limit: 64 MB Description Input Output Sample Input 4 ...
- ServiceStack.OrmLite破解
在 ServiceStack.OrmLite下的 OrmLiteConfigExtensions 第199行把这句注释掉就可以了 //LicenseUtils.AssertValidUsage(Lic ...
- 使用辗转相除法求两个数的最大公因数(python实现)
数学背景: 整除的定义: 任给两个整数a,b,其中b≠0,如果存在一个整数q使得等式 a = bq 成立,我们就说是b整除 ...