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的更多相关文章

  1. React Hook:使用 useEffect

    React Hook:使用 useEffect 一.描述 二.需要清理的副作用 1.在 class 组件中 2.使用 effect Hook 的示例 1.useEffect 做了什么? 2.为什么在组 ...

  2. React Hooks --- useState 和 useEffect

    首先要说的一点是React Hooks 都是函数,使用React Hooks,就是调用函数,只不过不同的Hooks(函数)有不同的功能而已.其次,React Hooks只能在函数组件中使用,函数组件也 ...

  3. React中useLayoutEffect和useEffect的区别

    重点: 1.二者函数签名相同,调用方式是一致的 2. 怎么简单进行选择: 无脑选择useEffect,除非运行效果和你预期的不一致再试试useLayoutEffect 区别详解:useEffect是异 ...

  4. React Native Android原生模块开发实战|教程|心得|怎样创建React Native Android原生模块

    尊重版权,未经授权不得转载 本文出自:贾鹏辉的技术博客(http://blog.csdn.net/fengyuzhengfan/article/details/54691503) 告诉大家一个好消息. ...

  5. 如何使用TDD和React Testing Library构建健壮的React应用程序

    如何使用TDD和React Testing Library构建健壮的React应用程序 当我开始学习React时,我努力的一件事就是以一种既有用又直观的方式来测试我的web应用程序. 每次我想测试它时 ...

  6. [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 ...

  7. react创建项目后运行npm run eject命令将配置文件暴露出来时报错解决方法

    最近在用create-react-app创建项目,因要配置各种组件,比如babel,antd等, 需要运行npm run eject命令把项目的配置文件暴露出来,但是还是一如既然碰到报错,因为是在本地 ...

  8. react+ant design 项目执行yarn run eject 命令后无法启动项目

    如何将内建配置全部暴露? 使用create-react-app结合antd搭建的项目中,项目目录没有该项目所有的内建配置, 1.执行yarn run eject 执行该命令后,运行项目yarn sta ...

  9. React 16 源码瞎几把解读 【二】 react组件的解析过程

    一.一个真正的react组件编译后长啥样? 我们瞎几把解读了react 虚拟dom对象是怎么生成的,生成了一个什么样的解构.一个react组件不光由若干个这些嵌套的虚拟dom对象组成,还包括各种生命周 ...

随机推荐

  1. 洛谷P0248 [NOI2010] 超级钢琴 [RMQ,贪心]

    题目传送门 超级钢琴 题目描述 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐. 这架超级钢琴可以弹奏出n个音符,编号为1至n.第i个音符 ...

  2. Kail Linux渗透测试教程之ARP侦查Netdiscover端口扫描Zenmap与黑暗搜索引擎Shodan

    Kail Linux渗透测试教程之ARP侦查Netdiscover端口扫描Zenmap与黑暗搜索引擎Shodan ARP侦查工具——Netdiscover Netdiscover是一个主动/被动的AR ...

  3. javascript类型系统

    前面的话 除了对象之外,数组Array类型可能是javascript中最常用的类型了.而且,javascript中的数组与其他多数语言中的数组有着相当大的区别.本文将介绍javascript中的数组A ...

  4. python中对list去重的多种方法

    今天遇到一个问题,用了 itertools.groupby 这个函数.不过这个东西最终还是没用上. 问题就是对一个list中的新闻id进行去重,去重之后要保证顺序不变. 直观方法 最简单的思路就是: ...

  5. 【BZOJ 2656】2656: [Zjoi2012]数列(sequence) (高精度)

    2656: [Zjoi2012]数列(sequence) Time Limit: 2 Sec  Memory Limit: 128 MBSubmit: 1499  Solved: 786 Descri ...

  6. 谈HTTPS中间人攻击与证书校验(二)

    上文说到HTTPS的三次握手:http://www.cnblogs.com/wh4am1/p/6616851.html 不懂的再回头去看看 三.中间人攻击 https握手过程的证书校验环节就是为了识别 ...

  7. 【转载】Unicode 编码表

    转载备忘:Unicode 编码表 具体请移步: http://www.cnblogs.com/chenwenbiao/archive/2011/08/17/2142718.html

  8. hdu 4111 Alice and Bob 记忆化搜索 博弈论

    Alice and Bob Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pi ...

  9. 2015 UESTC 搜索专题B题 邱老师降临小行星 记忆化搜索

    邱老师降临小行星 Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/contest/show/61 Des ...

  10. C#如何用OpenFileDialog控件打开图片显示到PictureBox这个控件

    openFileDialog1.Filter = "图片文件|*.jpg|BMP图片|*.bmp|Gif图片|*.gif"; OpenFileDialog ofd = new Op ...