@testing-library/react => uses ReactDOM to render components for testing
@testing-library/user-event => simulate user actions for testing
@testing-library/dom => find elements in rendered components
jest => run tests and reports results
jsdom => simulate a browser for nodejs environment
jest finds all files ending with .spec or .test or in __test__ folder and runs
test() global function
vite-react-typescript-jest-setup
jsdom creates fake browser environment in nodejs env when render function call
screen object access elements in the fake dom
element query => find elements that components rendered
React Testing Library Query System => 48 functions
getAllByRole, getByRole => ARIA Role, the purpose of the element for disability with vision and screen reader
by default html tag assigned role automatically => implicit
can assign role manually => explicit
heading => h1,h2,h3,h4,h5,h6 list => ul,li button => button link => a textbox => input default or type:text
role is preferred way to find elements
expect() with matchers (Jest + RTL) for assertion
Jest Matchers => JS related, toHaveLength(), toEqual(), toContain(), toThrow(), toHaveBeenCalled()
RTL Matchers => DOM related, toBeInTheDocument(), toBeEnabled(), toHaveClass(), toHaveTextContain(), toHaveValue()
knowing what to test + the best way to test
getAllByRole => multiple elements getByRole => only one element, throw error if not found or multiple, use for exact one element
user-event lib for user action
click(), keyboard(), keyboard('{Enter}')
jest can run specific test files
mock => fake => does not do anything, get called with arguments
mock function => jest.fn() => track number of calls and arguments
screen.logTestingPlaygroundURL() => open playground link
to know how to find proper query functions by selecting dom element in playground
thead, tbody => rowgroup tr => row th => columnheader td => cell
role cannot be good for every query, so use fallbacks => data-testid, container.querySelector()
data-testid => identifier for element => within(screen.getByTestId(id)) => not the best idea
render() returns an object which contains container which is a dom reference of the component wrapped by a div
container.querySelector() => direct access
avoid beforeEach for rendering components
npx rtl-book serve fileName.ts
RTL Book for cheat sheet
a => link button => button footer => contentinfo h1 => heading header => banner img => img input:type=checkbox => checkbox input:type=number => spinbutton input:type=radio => radio input:type=text => textbox li => listitem ul => list
text content within the element
screen.getByRole('button', { name: /sign in/i });
Self-closing elements (also known as 'void elements') like input
, img
, and br
cannot contain text content.
for accessible name of input, use label tag and link with input id and label htmlFor. Label text content will be accessible name of input.
const emailInput = screen.getByRole('textbox', { name: /email/i });
void element or element without text content, use aria-label
to give accessible names.
query functions => use to find elements that are rendered by components
All query functions are accessed through the screen
object in a test. These query functions always begin with one of the following names: getBy
, getAllBy
, queryBy
, queryAllBy
, findBy
, findAllBy
.
Start of Function Name | Examples |
---|---|
getBy | getByRole, getByText |
getAllBy | getAllByText, getByDisplayValue |
queryBy | queryByDisplayValue, queryByTitle |
queryAllBy | queryAllByTitle, queryAllByText |
findBy | findByRole, findBytext |
findAllBy | findAllByText, findAllByDisplayValue |
These names indicate the following:
- Whether the function will return an element or an array of elements
- What happens if the function finds 0, 1, or > 1 of the targeted element
- Whether the function runs instantly (synchronously) or looks for an element over a span of time (asynchronously)
Name | 0 matches | 1 match | > 1 match | Notes |
---|---|---|---|---|
getBy | Throw | Element | Throw | |
queryBy | null | Element | Throw | |
findBy | Throw | Element | Throw | Looks for an element over the span of 1 second |
Name | 0 matches | 1 match | > 1 match | Notes |
---|---|---|---|---|
getAllBy | Throw | []Element | []Element | |
queryAllBy | [ ] | []Element | []Element | |
findAllBy | Throw | []Element | []Element | Looks for elements over the span of 1 second |
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 |
findBy, findAllBy => promise resolve/reject => default 1s => use for data fetching or asynchronous
React Testing Library provides many different query functions. Each begins with a name like getBy
, findBy
, etc. The names also have common endings. The different name endings indicate how the query for an element will be performed.
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 |
Always prefer using query functions ending with ByRole
. Only use others if ByRole
is not an option.
accept regex
Matchers help make sure that a value is what we expect it to be.
A project generated by Create React App has access to all the matchers included in Jest, as well as matchers defined in the @testing-library/jest-dom package.
Name | Link |
---|---|
Jest | [https://jestjs.io/docs/mock-function-api] |
@testing-library/jest-dom | [https://github.com/testing-library/jest-dom] |
can create custom matchers, first parameter is expect element and others are matcher arguments
Most of testing is about figuring out other engineers' code
Testing in dream => write code => write test immediately => super easy
Testing in reality => bug report to support team => support team creates tickets and report to PM => PM request to the engineering team => engineers fix the bug and write tests
Library code needs library config for testing
act() warning => data fetching in useEffect()
unexpected state updates
in test are bad => test before state update => need to wait state update/asynchronousAct
function defines a window in time where state update occur => state update will happen in act function => act function fromreact-dom
without RTL- RTL uses
act
function automatically => screen.findBy, screen.findAllBy, waitFor, user.keyboard, user.click - don't follow warnings message, use findBy
options to solve act
warnings, from best to worst
- use
findBy
orfindAllBy
- use
act
function - use
module mock
to avoid rendering - use
act
as a pause
jest.mock(filePath, callback) => fake contents of a file
act
=> original react-dom => RTL import, modify and export
toHaveAttribute(attribute, value), toHaveClass(className)
don't make actual network request, due to slow and data changes in testing environment, mock them
- Mock file with data fetching code
- use msw (mock service worker) library to mock axios => intercept the request and return fake data
- create manual mock for axios
jest runs top-level codes first and then test code.
test nesting => describe() => groups together several related tests => beforeAll, afterAll related to describe block scope
debugging
- use test.only or describe.only to limit the number of tests executed => skip other tests
- use debugger => script =>
react-scripts --inspect-brk test --runInBand --no-cache
, chrome => about:inspect
cache can be problem for testing