zoukankan      html  css  js  c++  java
  • [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(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,
      })
    })
  • 相关阅读:
    httpclient 发送 json数据,微信security.msgSecCheck,
    win10 输入法,添加美式键盘,调整顺序
    activiti与spring的集成
    spring 中实例化Bean的三种方式
    工作流activiti的HelloWorld
    工作流Activiti的前置准备工作。
    Eclipse中配置约束(DTD,XSD)
    微信开发中,本地缓存,不同步的问题
    微信开发中碰到的坑,json传值,前台遍历json对象,跨页面转值,navigate层级限制
    用pageInfo对List手工分页
  • 原文地址:https://www.cnblogs.com/Answer1215/p/10403911.html
Copyright © 2011-2022 走看看