Тестирование фронтенда

Теория: Testing Library Best Practice

Testing Library Best Practice

Hexlet


Ограничения


import { render, screen, fireEvent } from '@testing-library/react'

test('clicks on disabled button', () => {
  const handleClick = jest.fn()

  render(
    <button style={{ pointerEvents: 'none' }} onClick={handleClick}>
      Save
    </button>,
  )

  fireEvent.click(screen.getByRole('button'))
  expect(handleClick).not.toBeCalled()
})

width


test('styles', () => {
  const handleClick = jest.fn()

  render(
    <div style={{ position: 'absolute', left: '-300%' }}>
      <button onClick={handleClick}>
        Save
      </button>
    </div>,
  )

  fireEvent.click(screen.getByRole('button'))
  expect(handleClick).not.toBeCalled() // ❌
})

test('click on hidden button', () => {
  const handleClick = jest.fn()

  render(
    <button data-testid="button" style={{ display: 'none' }} onClick={handleClick}>
      Save
    </button>,
  )

  fireEvent.click(screen.getByTestId('button'))
  expect(handleClick).not.toBeCalled() // ❌
})

test('click on hidden button', () => {
  const handleClick = jest.fn()

  render(
    <button style={{ display: 'none' }} onClick={handleClick}>
      Save
    </button>,
  )

  fireEvent.click(screen.getByRole('button')) // ❌
  expect(handleClick).not.toBeCalled()
})

width


test('bounding client rect', async () => {
  const handleClick = jest.fn()

  render(
    <button id="button" style={{ width: 100, height: 100 }} onClick={handleClick}>
      Save
    </button>,
  )

  const button = screen.getByRole('button')
  expect(button.getBoundingClientRect().height).toBe(100)
  expect(button.getBoundingClientRect().width).toBe(100)
})

width


test('z-index', async () => {
  const handleClick = jest.fn()

  render(
    <>
      <button id="button" style={{ width: 100, height: 100 }} onClick={handleClick}>
        Save
      </button>
      <div style={{ position: 'absolute', left: 0, right: 0, top: 0, botton: 0, zIndex: 100 }}></div>
    </>,
  )

  fireEvent.click(screen.getByRole('button'))
  expect(handleClick).not.toBeCalled() // ❌
})

const ReadonlyInput = () => {
  const [value, setValue] = React.useState('')

  return (
    <input readOnly value={value} onChange={event => setValue(event.target.value)} />
  )
}

test('readonly input', async () => {
  render(
    <ReadonlyInput />,
  )

  fireEvent.change(screen.getByRole('textbox'), {
    target: { value: 'Hello' },
  })

  expect(screen.getByRole('textbox').value).toBe('')
})

width


  • стили
  • реальные возможности
  • обязательные поля

Best Practice


Использование обертки

// ❌
const wrapper = render(<Example prop="1" />)
wrapper.rerender(<Example prop="2" />)

Демонтирование деревьев в React, смонтированных при рендере

// ❌
import { render, screen, cleanup } from '@testing-library/react'
afterEach(cleanup)

// ❌
const { getByRole } = render(<Example />)
const errorMessageNode = getByRole('alert')
  • Используйте screen
    • не деструктурируйте
    • можно использовать screen.debug

const button = screen.getByRole('button', { name: /disabled button/i })

Используйте верные селекторы

<label>Username</label><input data-testid="username" />

<label for="username">Username</label><input id="username" type="text" />

Не используйте контейнер

// ❌
const { container } = render(<Example />)
const button = container.querySelector('.btn-primary')
expect(button).toHaveTextContent(/click me/i)

Не рекомендуется

  • button
  • .btn.btn-large
  • #main

Селекторы по тексту

// ❌
screen.getByTestId('submit-button')

Выбирайте элементы по информативному названию aria-свойств, которые читают скринридеры Работает, даже если текстовое содержимое вашего элемента разбито на разные дочерние элементы

<button><span>Hello</span> <span>World</span></button>

Используйте find вместо waitFor

// ❌
const submitButton = await waitFor(() =>
  screen.getByRole('button', { name: /submit/i }),
)

Побочные эффекты в waitFor

You can't use snapshot assertions within waitFor

// ❌
await waitFor(() => {
  fireEvent.keyDown(input, { key: 'ArrowDown' })
  expect(screen.getAllByRole('listitem')).toHaveLength(3)
})

  • Используйте только query* для утверждений о том, что элемент не может быть найден
  • Используйте user-event
  • Используйте плагины для линтера для Testing Library
    • eslint-plugin-testing-library
    • eslint-plugin-jest-dom
Дальше

Завершено

0 / 8