[React Testing] React Testing library note

https://github.com/kentcdodds/react-testing-library-course/commit/1148e920b8d055d9ca9ef1c372e233d2e950ff1f

 

User-event:

https://testing-library.com/docs/ecosystem-user-event/

 

Jest-dom:

https://github.com/testing-library/jest-dom#with-typescript

 

jest-axe

https://github.com/nickcolley/jest-axe

 

getBy* vs queryBy*

getBy*: if not found, throw error, sometime you don't want test fail because no element found.

queryBy*: if not found, return null, so you can do .toBeNull()

 

Mock

import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {reportError as mockReportError} from '../api'
import {ErrorBoundary} from '../error-boundary'

jest.mock('../api')

beforeAll(() => {
  jest.spyOn(console, 'error').mockImplementation(() => {})
})

afterAll(() => {
  console.error.mockRestore()
})

afterEach(() => {
  jest.clearAllMocks()
})

function Bomb({shouldThrow}) {
  if (shouldThrow) {
    throw new Error('💣')
  } else {
    return null
  }
}

test('calls reportError and renders that there was a problem', async () => {
  mockReportError.mockResolvedValueOnce({success: true})
  const {rerender} = render(<Bomb />, {wrapper: ErrorBoundary})

  rerender(<Bomb shouldThrow={true} />)

  const error = expect.any(Error)
  const info = {componentStack: expect.stringContaining('Bomb')}
  expect(mockReportError).toHaveBeenCalledWith(error, info)
  expect(mockReportError).toHaveBeenCalledTimes(1)

  expect(console.error).toHaveBeenCalledTimes(2)

  expect(screen.getByRole('alert').textContent).toMatchInlineSnapshot(
    `"There was a problem."`,
  )

  console.error.mockClear()
  mockReportError.mockClear()

  rerender(<Bomb />)

  await userEvent.click(screen.getByText(/try again/i))

  expect(mockReportError).not.toHaveBeenCalled()
  expect(console.error).not.toHaveBeenCalled()
  expect(screen.queryByRole('alert')).not.toBeInTheDocument()
  expect(screen.queryByText(/try again/i)).not.toBeInTheDocument()
})

 

Mock a component from 3rd-party library, skip 1000ms transition, make test run faster

import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {HiddenMessage} from '../hidden-message'

jest.mock('react-transition-group', () => {
  return {
    CSSTransition: (props) => (props.in ? props.children : null),
  }
})

test('shows hidden message when toggle is clicked', async () => {
  const myMessage = 'hello world'
  render(<HiddenMessage>{myMessage}</HiddenMessage>)
  const toggleButton = screen.getByText(/toggle/i)
  expect(screen.queryByText(myMessage)).not.toBeInTheDocument()
  await userEvent.click(toggleButton)
  expect(screen.getByText(myMessage)).toBeInTheDocument()
  await userEvent.click(toggleButton)
  expect(screen.queryByText(myMessage)).not.toBeInTheDocument()
})

 

http jest mock:

import React from 'react'
import {render, screen, waitFor} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {loadGreeting as mockLoadGreeting} from '../api'
import {GreetingLoader} from '../greeting-loader-01-mocking'

jest.mock('../api')

test('loads greetings on click', async () => {
  const testGreeting = 'TEST_GREETING'
  mockLoadGreeting.mockResolvedValueOnce({data: {greeting: testGreeting}})
  render(<GreetingLoader />)
  const nameInput = screen.getByLabelText(/name/i)
  const loadButton = screen.getByText(/load/i)
  await userEvent.type(nameInput, 'Mary')
  await userEvent.click(loadButton)
  expect(mockLoadGreeting).toHaveBeenCalledWith('Mary')
  expect(mockLoadGreeting).toHaveBeenCalledTimes(1)
  await waitFor(() =>
    expect(screen.getByLabelText(/greeting/i)).toHaveTextContent(testGreeting),
  )
})

 

 msw for mock http server

npm i whatwg-fetch

npm i msw

import 'whatwg-fetch'
import * as React from 'react'
import {render, screen, waitFor} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {rest} from 'msw'
import {setupServer} from 'msw/node'
import {GreetingLoader} from '../greeting-loader-01-mocking'

const server = setupServer(
  rest.post('/greeting', (req, res, ctx) => {
    return res(ctx.json({data: {greeting: `Hello ${req.body.subject}`}}))
  }),
)

beforeAll(() => server.listen({onUnhandledRequest: 'error'}))
afterAll(() => server.close())
afterEach(() => server.resetHandlers())

test('loads greetings on click', async () => {
  render(<GreetingLoader />)
  const nameInput = screen.getByLabelText(/name/i)
  const loadButton = screen.getByText(/load/i)
  userEvent.type(nameInput, 'Mary')
  userEvent.click(loadButton)
  await waitFor(() =>
    expect(screen.getByLabelText(/greeting/i)).toHaveTextContent('Hello Mary'),
  )
})

 

Test Unmounting a React Component with React Testing Library

Sometimes your react component may need to do some cleanup work in the return value from useEffect or useLayoutEffect, or the componentWillUnmount lifecycle method for class components. Luckily for us, as far as React Testing Library is concerned, whichever of those you use is an implementation detail, so your test looks exactly the same regardless of how you implement it. Check out how you can test that simply with React Testing Library and a <Countdown /> component.

import React from 'react'
import {render, act} from '@testing-library/react'
import {Countdown} from '../countdown'

beforeAll(() => {
  jest.spyOn(console, 'error').mockImplementation(() => {})
})

afterAll(() => {
  console.error.mockRestore()
})

afterEach(() => {
  jest.clearAllMocks()
  jest.useRealTimers()
})

test('does not attempt to set state when unmounted (to prevent memory leaks)', () => {
  jest.useFakeTimers() // we don't want to wait 10 seconds
  const {unmount} = render(<Countdown />)
  unmount()
  act(() => jest.runOnlyPendingTimers()) // Using act with useFakeTimers()
  expect(console.error).not.toHaveBeenCalled()
})

 

Improve Reliability of Integration Tests using find* Queries from React Testing Library

By using some of the get queries, we’re assuming that those elements will be available on the page right when we execute the query. This is a bit of an implementation detail and it’d be cool if we could not make that assumption in our test. Let’s swap all those for find queries.

  await userEvent.click(await findByText(/fill.*form/i))
  await userEvent.click(await screen.findByText(/fill.*form/i))

 

Router testing

import React from 'react'
import {BrowserRouter} from 'react-router-dom'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {Main} from '../main'

test('main renders about and home and I can navigate to those pages', () => {
  window.history.pushState({}, 'Test page', '/')
  render(
    <BrowserRouter>
      <Main />
    </BrowserRouter>,
  )
  expect(screen.getByRole('heading')).toHaveTextContent(/home/i)
  userEvent.click(screen.getByText(/about/i))
  expect(screen.getByRole('heading')).toHaveTextContent(/about/i)
})

test('landing on a bad page shows no match component', () => {
  window.history.pushState({}, 'Test page', '/something-that-does-not-match')
  render(
    <BrowserRouter>
      <Main />
    </BrowserRouter>,
  )
  expect(screen.getByRole('heading')).toHaveTextContent(/404/i)
})

 

Write a Setup Function to Reduce Duplication of Testing Custom React Hooks

import React from 'react'
import {render, act} from '@testing-library/react'
import {useCounter} from '../use-counter'

function setup({initialProps} = {}) {
  const result = {}
  function TestComponent(props) {
    result.current = useCounter(props)
    return null
  }
  render(<TestComponent {...initialProps} />)
  return result
}

test('exposes the count and increment/decrement functions', () => {
  const result = setup()
  expect(result.current.count).toBe(0)
  act(() => result.current.increment())
  expect(result.current.count).toBe(1)
  act(() => result.current.decrement())
  expect(result.current.count).toBe(0)
})

test('allows customization of the initial count', () => {
  const result = setup({initialProps: {initialCount: 3}})
  expect(result.current.count).toBe(3)
})

test('allows customization of the step', () => {
  const result = setup({initialProps: {step: 2}})
  expect(result.current.count).toBe(0)
  act(() => result.current.increment())
  expect(result.current.count).toBe(2)
  act(() => result.current.decrement())
  expect(result.current.count).toBe(0)
})

test('the step can be changed', () => {
  const {result, rerender} = renderHook(useCounter, {
    initialProps: {step: 3},
  })
  expect(result.current.count).toBe(0)
  act(() => result.current.increment())
  expect(result.current.count).toBe(3)
  rerender({step: 2})
  act(() => result.current.decrement())
  expect(result.current.count).toBe(1)
})

 

Test React Portals with within from React Testing Library

 When you use a React Portal, much of your component can be rendered outside the main DOM tree. Let’s see how we can use utilities provided by React Testing Library to allow us to select elements within the portal. To perform actions and assert on what’s rendered.



import React from 'react'
import {render, within} from '@testing-library/react'
import {Modal} from '../modal'

test('modal shows the children', () => {
  render(
    <Modal>
      <div data-testid="test" />
    </Modal>,
  )
  const {getByTestId} = within(document.getElementById('modal-root')) // within: only look for elements inside Modal
  expect(getByTestId('test')).toBeInTheDocument()
})

 

posted @   Zhentiw  阅读(62)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2020-10-30 [Kotlin] Try resource, use {} block
2020-10-30 [Kotlin] Try catch & Throw
2020-10-30 [Kotlin] mutableList with toList
2018-10-30 [React] Use React.memo with a Function Component to get PureComponent Behavior
2018-10-30 [Javascript] Use a custom sort function on an Array in Javascript
2017-10-30 [Anuglar & NgRx] StoreRouterConnectingModule
2017-10-30 [Unit testing] data-test attr FTW
点击右上角即可分享
微信分享提示