skip to content

Search

React Testing Library Notes

4 min read

Foundational knowledge for effectively using React Testing Library with Jest.

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

const submitButton = screen.getByRole('button', {
  name: /submit/i
})
  • Fallbacks:
    • getByTestId
    • container.querySelector(All)

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

  • user from @testing-library/user-event is preferred over fireEvent
  • is an async function, therefore user methods need await

within

const rows = within(screen
  .getByTestId('users')
  .getAllByRole('row')
)
  • within an element with a test id of users, get all the elements with a role of row
  • helps to exclude rows you don’t want outside of the element with test id of users

render

  • render(<Component /> returns a { container }
  • container is automatically created to wrap the component
  • useful for when using container.querySelector

logTestingPlaygroundURL

  • screen.logTestingPlaygroundURL() opens a browser window which helps you select elements

avoiding beforeEach

  • a global variable built in to Jest
  • Jest runs the arrow function inside beforeEach before each individual test
  • React Testing Library does not recommend rendering components in 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

  • when you can’t use plain text
// 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

  • getBy___ - looks for exactly 1 element. If not found, returns an error.
  • queryBy___ - returns null.
  • findBy___ - works asynchronously. By default, it will watch the output of your component for 1 second. Throws an error and returns a Promise that gets rejected.

Finding 1 element

  • getBy___ - looks for exactly 1 element.
  • queryBy___ - looks for exactly 1 element.
  • findBy___ - asynchronously looks for exactly 1 element for 1 second.

Find > 1 element

  • getBy___ - returns error.
  • queryBy___ - returns null.
  • findBy___ - Throws an error and returns a Promise that gets rejected after 1 second.

Multiple Element Variations

Finding 0 elements

  • getAllBy___ - throws an error
  • queryAllBy___ - returns an empty array
  • findAllBy___ - throws an error

Finding 1 element

  • getAllBy___ - returns an array
  • queryAllBy___ - returns an array
  • findAllBy___ - returns an array

Finding > 1 element

  • getAllBy___ - returns an array
  • queryAllBy___ - returns an array
  • findAllBy___ - returns an array

When to use each

Goal of testUse
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 nameSearch Criteria
ByRoleFinds elements based on their implicit or explicit ARIA role
ByLabelTextFind form elements based upon the text their paired labels contain
ByPlaceholderTextFind form elements based upon their placeholder text
ByTextFind elements based upon the text they contain
ByDisplayValueFind elements based upon their current value
ByAltTextFind elements based upon their alt attribute
ByTitleFind elements based upon their title attribute
ByTestIdFind 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

  • helps avoid brittle tests for when you’re looking for a specific number of elements, but may add more later which will break the test
// 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)
})