Testing Library: user-event & jest-dom
user-event v14
user-event simulates real browser interactions more accurately than fireEvent. It triggers all intermediate events (pointerover, pointerenter, focus, click, input, change, blur) just like a real browser would.
import userEvent from '@testing-library/user-event'
// Setup — create an instance (tracks pointer/keyboard state across calls)
const user = userEvent.setup()
// Typing
await user.type(element, 'hello') // fires keydown/keypress/input/keyup per char
await user.type(element, '{Enter}') // special key
await user.type(element, 'hello{Tab}') // text then Tab
// Clear and type (replace value)
await user.clear(element)
await user.type(element, 'new value')
// Click
await user.click(element)
await user.dblClick(element)
await user.tripleClick(element) // select all text in input
// Right click / special clicks
await user.pointer({ keys: '[MouseRight]', target: element })
// Keyboard
await user.keyboard('hello') // types without focus events
await user.keyboard('{Tab}')
await user.keyboard('{Shift>}A{/Shift}') // shift+A
await user.keyboard('{Control>}a{/Control}') // ctrl+A (select all)
// Select
await user.selectOptions(select, 'value') // single select
await user.selectOptions(multiSelect, ['a', 'b']) // multi-select
await user.deselectOptions(multiSelect, 'a')
// File upload
const file = new File(['file contents'], 'photo.jpg', { type: 'image/jpeg' })
await user.upload(fileInput, file)
await user.upload(fileInput, [file1, file2]) // multiple files
// Hover
await user.hover(element)
await user.unhover(element)
// Tab navigation
await user.tab() // focus next focusable element
await user.tab({ shift: true }) // focus previousfireEvent — when to use
import { fireEvent } from '@testing-library/react'
// fireEvent fires a single synthetic event (no pointer/focus cascade)
// Use when userEvent doesn't support a specific event, or for performance
fireEvent.click(element)
fireEvent.change(input, { target: { value: 'new value' } })
fireEvent.submit(form)
fireEvent.keyDown(element, { key: 'Escape', code: 'Escape' })
fireEvent.scroll(scrollContainer, { target: { scrollTop: 100 } })
// Custom events
fireEvent(element, new MouseEvent('click', { bubbles: true, cancelable: true }))jest-dom Custom Matchers
// npm install --save-dev @testing-library/jest-dom
// import '@testing-library/jest-dom' in setup file
// Existence
expect(el).toBeInTheDocument()
expect(el).not.toBeInTheDocument()
// Visibility
expect(el).toBeVisible() // visible (not hidden by CSS or ancestors)
expect(el).not.toBeVisible()
// State
expect(el).toBeDisabled()
expect(el).toBeEnabled()
expect(el).toBeChecked() // checkbox/radio
expect(el).not.toBeChecked()
expect(el).toBeRequired()
expect(el).toBeValid() // form validation
expect(el).toBeInvalid()
expect(el).toHaveFocus()
expect(el).toBeEmptyDOMElement() // no children
// Content
expect(el).toHaveTextContent('Hello')
expect(el).toHaveTextContent(/hello/i)
expect(el).toHaveTextContent('Hello', { normalizeWhitespace: true })
// Value
expect(el).toHaveValue('alice@example.com')
expect(el).toHaveValue(['option1', 'option2']) // multi-select
expect(el).toHaveDisplayValue('Selected Option')
// Attributes & style
expect(el).toHaveAttribute('type', 'submit')
expect(el).toHaveAttribute('aria-expanded', 'false')
expect(el).toHaveClass('active')
expect(el).toHaveClass('btn', 'primary') // multiple classes
expect(el).toHaveStyle({ display: 'none' })
expect(el).toHaveStyle('color: red; font-size: 14px')
// ARIA
expect(el).toHaveAccessibleName('Close dialog')
expect(el).toHaveAccessibleDescription('This action cannot be undone')Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free