Testing Library
02 / 03

user-event & jest-dom

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 previous

fireEvent — 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