React Testing Library Notes

Foundational knowledge for effectively using React Testing Library with Jest.

June 14, 2024 | Not my art.

Using getByRole is preferred, but sometimes it will not be ideal

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

Mock functions

Fake functions that don’t do anything

const mockProps = {
    onSwitchToggle: jest.fn(),
}

Fake functions records whenever it gets called, and the arguments it was called with - toHaveBeenCalled(), toHaveBeenCalledWith().

They are used to make sure a component calls a callback function.

User

within

const rows = within(screen.getByTestId('users').getAllByRole('row'))

render

logTestingPlaygroundURL

avoiding beforeEach

Partial list of commonly used roles:

const roles = [
    'link',           // <a href="/"></a>
    'button',         // <button> 
    'contentinfo',    // <footer>
    'heading',        // <h1>
    'banner',         // <header>
    'img',            // <img />
    'checkbox',       // <input type="checkbox" />
    'spinbutton',     // <input type="number" />
    'radio',          // <input type="radio" />
    'textbox',        // <input type="text" />
    'listitem',       // <li>
    'list',           // <ul>
  ]

for (let role of roles) {
    const el = screen.getByRole(role);
    expect(el).toBeInTheDocument()
  }

Finding by Accessible Names

// Component
<div>
  <button>Submit</button>
  <button>Cancel</button>
</div>

test('can select by accessible name', () => {
  render(<Component />)

  const submitButton = screen.getByRole('button', { name: /submit/i })
  const cancelButton = screen.getByRole('button', { name: /cancel/i })
})

Directly Assigning an Accessible Name

// Component
<div>
  <button aria-label="sign in"><svg /></button>
  <button aria-label="sign out"><svg /></button>
</div>

test('can select by accessible name', () => {
  render(<Component />)

  const signInButton = screen.getByRole('button', { name: /sign in/i })
  const signOutButton = screen.getByRole('button', { name: /sign out/i })
})

GetBy, QueryBy, FindBy

Finding 0 elements

Finding 1 element

Find > 1 element

Multiple Element Variations

Finding 0 elements

Finding 1 element

Finding > 1 element

When to use each

Goal of test Use
Prove an element exists ‎ ‎ ‎ ‎ getBy, getAllBy
Prove an element does not exist ‎ ‎ ‎ ‎ queryBy, queryAllBy
Make sure an element eventually exists ‎ ‎ ‎ ‎ findBy, findAllBy

Querying for Elements with Different Criteria

End of function name Search Criteria
ByRole Finds elements based on their implicit or explicit ARIA role
ByLabelText Find form elements based upon the text their paired labels contain
ByPlaceholderText Find form elements based upon their placeholder text
ByText Find elements based upon the text they contain
ByDisplayValue Find elements based upon their current value
ByAltText Find elements based upon their alt attribute
ByTitle Find elements based upon their title attribute
ByTestId Find elements based upon their data-testid attribute

When to use each of these

Always prefer using query functions ending with ByRole. Only use others if ByRole is not an option.

Matchers

within is used for looking for elements inside of another element

// Component
<div>
	<button>Go back</button>
	<form aria-label="form">
	  <button>Submit</button>
	  <button>Cancel</button>
	</form>
</div>

test('can get buttons within form only', () => {
  render(<Component />)

  const form = screen.getByRole('form')
  const formButtons = within(form).getAllByRole('button')

  expect(formButtons).toHaveLength(2) 
})