[React] Ensure all React useEffect Effects Run Synchronously in Tests with react-testing-library
React in with all run library Testing Effects
2023-09-14 09:00:49 时间
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, useEffect
is 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(2) expect(children).toHaveBeenCalledWith({ data: null, error: null, fetching: true, loaded: false, }) expect(client.request).toHaveBeenCalledTimes(1) expect(client.request).toHaveBeenCalledWith(query, variables) children.mockClear() await wait() expect(children).toHaveBeenCalledTimes(1) 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(0) expect(children).toHaveBeenCalledTimes(1) // 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(1) 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(1) 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中的diff算法,通俗易懂的解读
- React报错之Expected `onClick` listener to be a function
- react中什么情况下不能用index作为key
- react-navigation重复点击多次跳转的解决方案
- react面试如何回答才能让面试官满意
- React组件生命周期
- 深度分析React源码中的合成事件
- React性能优化的8种方式
- 滴滴前端高频react面试题汇总_2023-02-27
- 使用react修改ant design默认样式|自定义
- React源码分析之深入理解fiber
- React源码之-commit阶段
- 如何进行react状态管理方案选择
- React 合成事件的源码实现
- 深入react源码看setState究竟做了什么?
- 校招前端二面经典react面试题及答案_2023-03-13
- react 基础之组件篇二——Style in React
- 前端 CST和GMT+0800时间转换(js/vue/react/jsp通用)
- in Neo4j查询:使用Not In操作(neo4j查询not)
- 限制Oracle IN语句元素数量限制(oracle的in个数)
- 一个资深iOS开发者对于React Native的看法
- 如何在MySQL中代替IN关键字(mysql中代替in)
- MySQL中IN语句的限制详解(mysql中in的限制)
- MySQL中使用IN子句出现重复值问题的解决方法(mysql中in有重复值)
- MySQL中IN语句的参数化使用方法(mysql中in参数化)
- 符号Oracle 中与 IN 的区别(Oracle中=和in)