arrow-left

All pages
gitbookPowered by GitBook
1 of 12

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Example

hashtag
Full Example

See the following sections for a detailed breakdown of the test

We recommend using Mock Service Workerarrow-up-right library to declaratively mock API communication in your tests instead of stubbing window.fetch, or relying on third-party adapters.


hashtag
Step-By-Step

hashtag
Imports

hashtag
Mock

Use the setupServer function from msw to mock an API request that our tested component makes.

hashtag
Arrange

The render method renders a React element into the DOM.

hashtag
Act

The fireEvent method allows you to fire events to simulate user actions.

hashtag
Assert

hashtag
System Under Test

import React from "react";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import Fetch from "../fetch";

const server = setupServer(
  rest.get("/greeting", (req, res, ctx) => {
    return res(ctx.json({ greeting: "hello there" }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test("loads and displays greeting", async () => {
  render(<Fetch url="/greeting" />);

  fireEvent.click(screen.getByText("Load Greeting"));

  await waitFor(() => screen.getByRole("heading"));

  expect(screen.getByRole("heading")).toHaveTextContent("hello there");
  expect(screen.getByRole("button")).toBeDisabled();
});

test("handles server error", async () => {
  server.use(
    rest.get("/greeting", (req, res, ctx) => {
      return res(ctx.status(500));
    })
  );

  render(<Fetch url="/greeting" />);

  fireEvent.click(screen.getByText("Load Greeting"));

  await waitFor(() => screen.getByRole("alert"));

  expect(screen.getByRole("alert")).toHaveTextContent("Oops, failed to fetch!");
  expect(screen.getByRole("button")).not.toBeDisabled();
});
// import dependencies
import React from "react";

// import API mocking utilities from Mock Service Worker
import { rest } from "msw";
import { setupServer } from "msw/node";

// import react-testing methods
import { render, fireEvent, waitFor, screen } from "@testing-library/react";

// add custom jest matchers from jest-dom
import "@testing-library/jest-dom";
// the component to test
import Fetch from "../fetch";
test("loads and displays greeting", async () => {
  // Arrange
  // Act
  // Assert
});
// declare which API requests to mock
const server = setupServer(
  // capture "GET /greeting" requests
  rest.get("/greeting", (req, res, ctx) => {
    // respond using a mocked JSON body
    return res(ctx.json({ greeting: "hello there" }));
  })
);

// establish API mocking before all tests
beforeAll(() => server.listen());
// reset any request handlers that are declared as a part of our tests
// (i.e. for testing one-time error scenarios)
afterEach(() => server.resetHandlers());
// clean up once the tests are done
afterAll(() => server.close());

// ...

test("handles server error", async () => {
  server.use(
    // override the initial "GET /greeting" request handler
    // to return a 500 Server Error
    rest.get("/greeting", (req, res, ctx) => {
      return res(ctx.status(500));
    })
  );

  // ...
});
render(<Fetch url="/greeting" />);
fireEvent.click(screen.getByText("Load Greeting"));

// wait until the `get` request promise resolves and
// the component calls setState and re-renders.
// `waitFor` waits until the callback doesn't throw an error

await waitFor(() =>
  // getByRole throws an error if it cannot find an element
  screen.getByRole("heading")
);
// assert that the alert message is correct using
// toHaveTextContent, a custom matcher from jest-dom.
expect(screen.getByRole("alert")).toHaveTextContent("Oops, failed to fetch!");

// assert that the button is not disabled using
// toBeDisabled, a custom matcher from jest-dom.
expect(screen.getByRole("button")).not.toBeDisabled();
import React, { useState, useReducer } from "react";
import axios from "axios";

const initialState = {
  error: null,
  greeting: null,
};

function greetingReducer(state, action) {
  switch (action.type) {
    case "SUCCESS": {
      return {
        error: null,
        greeting: action.greeting,
      };
    }
    case "ERROR": {
      return {
        error: action.error,
        greeting: null,
      };
    }
    default: {
      return state;
    }
  }
}

export default function Fetch({ url }) {
  const [{ error, greeting }, dispatch] = useReducer(
    greetingReducer,
    initialState
  );
  const [buttonClicked, setButtonClicked] = useState(false);

  const fetchGreeting = async (url) =>
    axios
      .get(url)
      .then((response) => {
        const { data } = response;
        const { greeting } = data;
        dispatch({ type: "SUCCESS", greeting });
        setButtonClicked(true);
      })
      .catch((error) => {
        dispatch({ type: "ERROR", error });
      });

  const buttonText = buttonClicked ? "Ok" : "Load Greeting";

  return (
    <div>
      <button onClick={() => fetchGreeting(url)} disabled={buttonClicked}>
        {buttonText}
      </button>
      {greeting && <h1>{greeting}</h1>}
      {error && <p role="alert">Oops, failed to fetch!</p>}
    </div>
  );
}

Cheatsheet

Get the printable cheat sheetarrow-up-right

A short guide to all the exported functions in React Testing Library

  • render const {/* */} = render(Component) returns:

    • unmount function to unmount the component

    • container reference to the DOM node where the component is mounted

    • all the queries from DOM Testing Library, bound to the document so there is no need to pass a node as the first argument (usually, you can use the screen import instead)

hashtag
Queries

Difference from DOM Testing Library

The queries returned from render in React Testing Library are the same as DOM Testing Library except they have the first argument bound to the document, so instead of getByText(node, 'text') you do getByText('text')

See Which query should I use?

No Match
1 Match
1+ Match
Await?
  • ByLabelText find by label or aria-label text content

    • getByLabelText

    • queryByLabelText

hashtag
Async

The dom-testing-library Async API is re-exported from React Testing Library.

  • waitFor (Promise) retry the function within until it stops throwing or times out

  • waitForElementToBeRemoved (Promise) retry the function until it no longer returns a DOM node

hashtag
Events

See Events API

  • fireEvent trigger DOM event: fireEvent(node, event)

  • fireEvent.* helpers for default event types

    • click

hashtag
Other

See Querying Within Elements, Config API, Cleanup,

  • within take a node and return an object with all the queries bound to the node (used to return the queries from React Testing Library's render method): within(node).getByText("hello")

  • configure change global options: configure({testIdAttribute: 'my-data-test-id'})

hashtag
Text Match Options

Given the following HTML:

Will** find the div:**

Testing Input

hashtag
🎹 Testing Input

hashtag
🎹 Testing Input

Yes

queryBy

null

return

throw

No

getAllBy

throw

array

array

No

findAllBy

throw

array

array

Yes

queryAllBy

[]

array

array

No

getAllByLabelText

  • queryAllByLabelText

  • findByLabelText

  • findAllByLabelText

  • ByPlaceholderText find by input placeholder value

    • getByPlaceholderText

    • queryByPlaceholderText

    • getAllByPlaceholderText

    • queryAllByPlaceholderText

    • findByPlaceholderText

    • findAllByPlaceholderText

  • ByText find by element text content

    • getByText

    • queryByText

    • getAllByText

    • queryAllByText

    • findByText

    • findAllByText

  • ByDisplayValue find by form element current value

    • getByDisplayValue

    • queryByDisplayValue

    • getAllByDisplayValue

    • queryAllByDisplayValue

    • findByDisplayValue

    • findAllByDisplayValue

  • ByAltText find by img alt attribute

    • getByAltText

    • queryByAltText

    • getAllByAltText

    • queryAllByAltText

    • findByAltText

    • findAllByAltText

  • ByTitle find by title attribute or svg title tag

    • getByTitle

    • queryByTitle

    • getAllByTitle

    • queryAllByTitle

    • findByTitle

    • findAllByTitle

  • ByRole find by aria role

    • getByRole

    • queryByRole

    • getAllByRole

    • queryAllByRole

    • findByRole

    • findAllByRole

  • ByTestId find by data-testid attribute

    • getByTestId

    • queryByTestId

    • getAllByTestId

    • queryAllByTestId

    • findByTestId

    • findAllByTestId

  • fireEvent.click(node)
  • See all supported eventsarrow-up-right

  • act wrapper around react-dom/test-utils actarrow-up-right; React Testing Library wraps render and fireEvent in a call to act already so most cases should not require using it manually

  • cleanup clears the DOM (use with afterEach to reset DOM between tests)

    getBy

    throw

    return

    throw

    No

    findBy

    throw

    return

    Get the printable cheat sheetarrow-up-right

    throw

    import { render, fireEvent, screen } from "@testing-library/react";
    
    test("loads items eventually", async () => {
      render(<Page />);
    
      // Click button
      fireEvent.click(screen.getByText("Load"));
    
      // Wait for page to update with query text
      const items = await screen.findAllByText(/Item #[0-9]: /);
      expect(items).toHaveLength(10);
    });
    <div>Hello World</div>
    // Matching a string:
    getByText("Hello World"); // full string match
    getByText("llo Worl", { exact: false }); // substring match
    getByText("hello world", { exact: false }); // ignore case
    
    // Matching a regex:
    getByText(/World/); // substring match
    getByText(/world/i); // substring match, ignore case
    getByText(/^hello world$/i); // full string match, ignore case
    getByText(/Hello W?oRlD/i); // advanced regex
    
    // Matching with a custom function:
    getByText((content, element) => content.startsWith("Hello"));
    hashtag
    Input Event

    Note

    If you want to simulate a more natural typing behaviour while testing your component, consider the companion library [user-event](https: //testing-library.com/docs/ecosystem-user-event)

    hashtag
    🗺 Site Navigation

    arrow-up-right

    hashtag
    📖 Storybook

    Storybook is an open source tool for developing UI components in isolation. It essentially gives us a sandbox to showcase and test the components we'll use throughout our app.

    It allows us to develop more efficiently (we can develop our components in isolation within Storybook, rather than developing on the page of the app), and also allows the design and UI teams to interact with our components as we build them. They can also change the values of the component's props to see how the component will react as it receives different data.

    To get it up and running navigate to the app repo and, from the command line, enter:

    Let's first discuss some of the core principles of Storybook (we'll use examples from our own app where possible), before then diving into the anatomy of a typical story.

    hashtag
    Stories

    A story, much like a React component, is a function that describes how to render a component.

    You can have multiple stories per component, meaning Storybook gives us the power to describe multiple rendered states.

    For a very simple example using the PushDownPanel component from our own app, we might first describe the component with it's default state, but then add a story that describes a different rendered state.

    So here's how the initial default PushDownPanel might look:

    And then we can add variations based on different states, for example a version that uses icons:

    And the new stories will show up in the sidebar navigation, like so:

    hashtag
    Args

    Story definitions can be further improved to take advantage of Storybook's "args" concept.

    A story is a component with a set of arguments (props), and these arguments can be altered and composed dynamically. This gives Storybook its power (they even refer to this as a superpower in their docs!), allowing us essentially to live edit our components.

    Most of the time the type of arg will be [inferred automatically](https: //storybook.js.org/docs/react/api/argtypes#automatic-argtype-inference), however we can use ArgTypes to further configure the behavior of our args, constraining the values they can take and generating relevant UI controls.

    hashtag
    Controls

    Controls allow designers and developers to explore component behavior by mucking about with its arguments.

    Storybook feeds the given args property into the story during render. Each of the args from the story function will now be live editable using Storybook's Controls panel, so we can dynamically change components in Storybook to see how they look with different settings and data:

    This essentially evolves Storybook into an interactive documentation tool, allowing developers and stakeholders to stress test components, for example by adding huge strings of text that might help expose UI problems.

    Controls can be configured to use UI elements that we deem most appropriate according to the data that we're trying to manipulate; some examples from our own components include a radio button for manipulating background color:

    A select menu to control the number of items that render:

    And for our Hero component, a mixture of text inputs, boolean switch and radio group (screen shot of how these controls render follows the code):

    hashtag
    A11y and Other Addons

    Addons are plugins that extend Storybook's core functionality, packaged as NPM modules. Once [installed and registered](https: //storybook.js.org/docs/react/addons/install-addons) they will appear in the addons panel, a reserved place in the Storybook UI below the main component.

    One such addon we use is the Accessibility addon, which helps to make our UI components more accessible. Simply select the Accessibility tab from the aforementioned addons panel, and there you will see any Violations, Passes and Incomplete requirements pertaining to accessibility.

    We also use the Viewport [toolbar](https: //storybook.js.org/docs/react/get-started/browse-stories#toolbar) item, which allows us to adjust the dimensions of the iframe our stories are rendered in, making it nice to test responsive UIs.

    hashtag
    Anatomy of a Story

    Stories exist alongside the other component files as stories.js.

    Here's an example of how a typical story might take shape:

    Let's break this down line by line:

    Component Setup: So we start off with your normal component setup, standard stuff: importing React, importing your component and (if your component gets data from Sitecore) importing any data that might be required.

    Data Composition: After the component setup, we next move on to the process of essentially distilling the data recieved from Sitecore into only the parts we need - you can read more about this process in not only one spot of our docs, but two! Here and here.

    Exporting Stories: After composing the data, we move on to one of the key ingredients of a story: the default export that describes the component. It's a function that returns a component's state given a set of arguments; it describes how to render a component.

    It's a story! Behold. Don't get them wet, don't expose them to bright light, and most importantly don't feed them after midnight!

    Were we to add any other stories beyond the default, we would then add named exports that would describe the additional stories.

    We can also add ArgTypes here if we need to configure our args beyond Storybook's automatically-generated UI controls.

    In the example above, we're setting backgroundColor as a radio button so the user can choose between different background colors, and setting the default as white.

    Template Definition: Now that our stories are exported, we move on to defining a master template (Template) for our component's stories, and passing in our args.

    We can then reuse this template across stories. Template.bind({}) makes a copy of the function, reducing code duplication. This is a [standard JavaScript technique](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) for making a copy of a function, and allows each exported story to set its own properties.

    And finally we spread in our component's props (...props), making data available to our component as it would be in the regular app.

    hashtag
    storybookCustomArgs

    The way in which Storybook automatically infers a set of argTypes based on each component's props can often lead to a lot of unnecessary UI controls rendering.

    We have a function for that! In the lib/helpers/index.ts file you can find a very cool function called storybookCustomArgs, which allows us to configure which props we would like to render controls for in the Storybook Controls panel.

    hashtag
    createStoryOptions

    Oftentimes our component data will come through to us a single array, but in order for Storybook to render the controls in our desired way, we need that data to be a multi-dimenstional array.

    Again we have a nifty function for that! Here's an example of both storybookCustomArgs and createStoryOptions Storybook helper functions at work:

    hashtag
    Resources

    • [Learn Storybook](https: //www.learnstorybook.com) - a guided tutorial through building a simple application with Storybook

    • [Component Driven User Interfaces](https: //www.componentdriven.org) - learn more about the component-driven approach that Storybook enables

    • [Storybook Addons](https: //storybook.js.org/addons) - supercharge Storybook with advanced features and new workflows

    • [Component Story Format](https: //storybook.js.org/blog/component-story-format/) - read more about the Component Story Format (CSF), a new way to author stories based on ES6 modules

    Self Link

    https: //duke-2.gitbook.io/duke

    https://duke-3.gitbook.io/duke/arrow-up-right

    Duke Training

    Website Navigation


    Table of contents

    hashtag
    General Info

    hashtag
    Sitecore

    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2658%20(1).mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2659.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2658-1.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/dnt-2724-accordion-testing.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/2654-a11y-audit-form-errors.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/analytics.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/definitionofdone.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/technicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/practicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/sitecore.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/intro.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/setup.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/forms.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/tailwind-css.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/storybook.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/ideas.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/broken-reference/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessibility/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessibility/aria.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/ke-energy.mdchevron-right
    ⚙️Testingchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2658.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tickets/dnt-2659.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/dnt-2724-accordion-testing.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/jira-tasks/jira-tickets/2654-a11y-audit-form-errors.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/analytics.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/definitionofdone.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/technicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/practicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/sitecore.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/component-creation/intro.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/setup.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/forms.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/tailwind-css.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/storybook.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/ideas.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/broken-reference/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessibility/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessibility/aria.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript-types.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/types-vs-interfaces.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript-interfaces.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/enums.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/types-vs-interfaces-1.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/docs/typescript/typescript-rules/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessability/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessability/focus-order.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/accessability/aria-accessibility.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/testing/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/testing/testing-input.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/react-testing-library/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/most-useful/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/most-useful/tailwind-classes/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/most-useful/bookmarks.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/website/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/website/overview/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/website/overview/duke-energy-manual-audit_report.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/live-deploy.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/intro.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/technicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/practicaloverview.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/analytics.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/creating-components/sitecore/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/creating-components/sitecore/sitecore-docs.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/creating-components/definitionofdone.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/setup.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/testing.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/forms.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/storybook/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/storybook/official-tutorial/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/website/storybook/official-tutorial/component-driven-design.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/code-splitting.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/svgloader.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/tailwind-css.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/Website/unit-tests.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/hooks-api-reference-react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/using-the-effect-hook-react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/getting-started-with-react.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/react-todolist.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/componentizing-our-react-app.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/react-interactivity-events-and-state.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/react-interactivity-editing-filtering-conditional-rendering.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/create-react-app.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/react-components.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/react-notes/handling-events.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/testing-assignment/react-testing-recipes.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/webpack.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/curry-vs-functional-composition/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/curry-vs-functional-composition/argv.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/bubbling-and-capturing.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/modules.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/private-npm-packages.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/aria-labelledby-accessibility.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/focus.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/optional-chaning.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/promises.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/enums-in-javascript.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/jira.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/airbnb-javascript-style-guide.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/general-info/general-info/performance.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/sitecore/sitecore/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/sitecore/sitecore/dxt-solution.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/index.tsx.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/composition.tsx.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/data.js.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/types.ts.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/stories.js.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/code/code/test.tsx.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/personal-assignments/testing-assignment.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-2/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/README.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-2/day-4.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-2/day-2.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-2/day-3.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-3.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-4.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-1.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-5.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-4.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-2.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-1/week-4/day-3.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-5.mdchevron-right
    https://github.com/bgoonz/DUKE/blob/ghpages/testing/meetings/week-6.mdchevron-right

    ByLabelText

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    getByLabelText, queryByLabelText, getAllByLabelText, queryAllByLabelText, findByLabelText, findAllByLabelText

    hashtag
    API

    This will search for the label that matches the given TextMatch, then find the element associated with that label.

    The example below will find the input node for the following DOM structures:

    <Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { label: 'Cypress', value: 'cypress', }, ] }>

    hashtag
    Options

    hashtag
    name

    The example above does NOT find the input node for label text broken up by elements. You can use getByRole('textbox', { name: 'Username' }) instead which is robust against switching to aria-label or aria-labelledby.

    hashtag
    selector

    If it is important that you query an actual <label> element you can provide a selector in the options:

    Note

    getByLabelText will not work in the case where a for attribute on a <label> element matches an id attribute on a non-form element.

    ByTitle

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    getByTitle, queryByTitle, getAllByTitle, queryAllByTitle, findByTitle, findAllByTitle

    hashtag
    API

    Returns the element that has the matching title attribute.

    Common Mistakes With RTL

    hashtag

    Importance: medium

    If you'd like to avoid several of these common mistakes, then the official ESLint plugins could help out a lot:

    import React, {
        useState
    } from 'react'
    import {
        render,
        fireEvent
    } from '@testing-library/react'
    function CostInput() {
    
    const [ value, setValue ] = useState( '' ) removeDollarSign = value => ( value[ 0 ] === '$' ? value.slice( 1 ) : value ) getReturnValue = value => ( value === '' ? '' : `$${value}` ) handleChange = ev => {
            ev.preventDefault()
    const inputtedValue = ev.currentTarget.value
    
    const noDollarSign = removeDollarSign( inputtedValue ) if ( isNaN( noDollarSign ) ) return setValue( getReturnValue( noDollarSign ) )
        }
        return <input value = {
            value
        }
        aria - label = "cost-input"
        onChange = {
            handleChange
        }
        />}
    const setup = () => {
    const utils = render(<CostInput / > )
    const input = utils.getByLabelText( 'cost-input' ) return {
        input,
        ...utils,
    }
    }
    test( 'It should keep a $ in front of the input', () => {
    
    const {
                input
            } = setup() fireEvent.change( input, {
                target: {
                    value: '23'
                }
            } ) expect( input.value ).toBe( '$23' )
        } ) test( 'It should allow a $ to be in the input when the value is changed', () => {
    
    const {
                input
            } = setup() fireEvent.change( input, {
                target: {
                    value: '$23.0'
                }
            } ) expect( input.value ).toBe( '$23.0' )
        } ) test( 'It should not allow letters to be inputted', () => {
    
    const {
                    input
                } = setup() expect( input.value ).toBe( '' )
    // empty before  fireEvent.change(input, {target: {value: 'Good Day'}})  expect(input.value).toBe('')
    //empty after})test('It should allow the $ to be deleted', () => {
    const {input} = setup()  fireEvent.change(input, {target: {value: '23'}})  expect(input.value).toBe('$23')
    // need to make a change so React registers "" as a change  fireEvent.change(input, {target: {value: ''}})  expect(input.value).toBe('')})
    npm run storybook
    export const NoIcons = Template.bind();
    NoIcons.args = {
      items: itemsOptions["3"],
    };
    export const Icons = Template.bind({});
    Icons.args = {
      items: itemsOptions["5"],
    };
    export default {
      title: "Components/Accordion",
      component: AccordionComponent,
      argTypes: {
        theme: {
          name: "Theme",
        },
        closeOthers: {
          name: "One Panel Open At A Time",
        },
      },
    };
    
    const Template = (args) => <AccordionComponent {...args} />;
    
    export const Primary = Template.bind({});
    
    Primary.args = {
      ...props,
    };
    argTypes: {
      backgroundColor: {
        control: {
          type: 'radio',
          options: ['white', 'gray'],
        },
        defaultValue: 'white',
      },
    },
    argTypes: {
      items: {
        name: 'How many items?',
        control: {
          type: 'select',
          options: {
            ...itemsPrimary,
          },
        },
      },
    },
    argTypes: {
      title: {
        control: {
          type: 'text',
        },
      },
      subtitle: {
        control: {
          type: 'text',
        },
      },
      link: {
        control: 'boolean',
      },
      ctaType: {
        control: {
          type: 'inline-radio',
          options: ['ImageSlide', 'VideoSlide'],
        },
      },
    },
    import React from "react";
    import MyComponent from "./index";
    import { Data } from "./data";
    import { MyComponent as MyComponentComposition } from "../../lib/composition";
    
    const props = MyComponentComposition({ fields: Data });
    
    export default {
      title: "Components/MyComponent",
      component: MyComponent,
      argTypes: {
        backgroundColor: {
          control: {
            type: "radio",
            options: ["white", "gray"],
          },
          defaultValue: "white",
        },
      },
    };
    
    const Template = (args) => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      ...props,
    };
    import React from 'react';
    import MyComponent from './index';
    import { Data } from './data';
    
    ...
    ...
    
    import { MyComponent as MyComponentComposition } from '../../lib/composition';
    
    
    const props = MyComponentComposition({ fields: Data });
    
    ...
    ...
    
    export default {
      title: 'Components/MyComponent',
      component: MyComponent,
      argTypes: {
        backgroundColor: {
          control: {
            type: 'radio',
            options: ['white', 'gray'],
          },
          defaultValue: 'white',
        },
      },
    };
    
    ...
    ...
    
    
    const Template = args => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      ...props,
    };
    ...
    
    import { storybookCustomArgs, createStoryOptions } from 'src/lib/helpers';
    
    
    const props = BulletedOverviewComposition(Data);
    
    const itemsToShow = createStoryOptions(props.items);
    
    export default {
      title: 'Components/Bulleted Overview',
      component: BulletedOverview,
      argTypes: {
        ...storybookCustomArgs(props, BulletedOverview, [], false),
        items: {
          name: 'How many items?',
          control: {
            type: 'select',
            options: {
              ...itemsToShow,
            },
          },
        },
      },
    };
    
    ...
    getByLabelText(
      // If you're using `screen`, then skip the container argument:
      container: HTMLElement,
      text: TextMatch,
      options?: {
        selector?: string = '*',
        exact?: boolean = true,
        normalizer?: NormalizerFn,
      }): HTMLElement

    Will also find a title element within an SVG.

    <Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { label: 'Cypress', value: 'cypress', }, ] }>

    hashtag
    Options

    TextMatch options

    getByTitle(
      // If you're using `screen`, then skip the container argument:
      container: HTMLElement,
      title: TextMatch,
      options?: {
        exact?: boolean = true,
        normalizer?: NormalizerFn,
      }): HTMLElement
    <span title="Delete" id="2"></span>
    <svg>
      <title>Close</title>
      <g><path /></g>
    </svg>
    eslint-plugin-testing-libraryarrow-up-right
  • eslint-plugin-jest-domarrow-up-right

  • Note: If you are using create-react-app, eslint-plugin-testing-library is already included as a dependency.

    Advice: Install and use the ESLint plugin for Testing Library.

    hashtag
    Using wrapper as the variable name for the return value from renderarrow-up-right

    Importance: low

    The name wrapper is old cruft from enzyme and we don't need that here. The return value from render is not "wrapping" anything. It's simply a collection of utilities that (thanks to the next thing) you should actually not often need anyway.

    Advice: destructure what you need from render or call it view.

    hashtag
    Using cleanuparrow-up-right

    Importance: medium

    For a long time now cleanup happens automatically (supported for most major testing frameworks) and you no longer need to worry about it. Learn morearrow-up-right.

    Advice: don't use cleanup

    hashtag
    Not using screenarrow-up-right

    Importance: medium

    screen was added in DOM Testing Library v6.11.0arrow-up-right (which means you should have access to it in @testing-library/react@>=9). It comes from the same import statement you get render from:

    The benefit of using screen is you no longer need to keep the render call destructure up-to-date as you add/remove the queries you need. You only need to type screen. and let your editor's magic autocomplete take care of the rest.

    The only exception to this is if you're setting the container or baseElement which you probably should avoid doing (I honestly can't think of a legitimate use case for those options anymore and they only exist for historical reasons at this point).

    You can also call screen.debugarrow-up-right instead of debug

    Advice: use screen for querying and debugging.

    hashtag
    Using the wrong assertionarrow-up-right

    Importance: high

    That toBeDisabled assertion comes from jest-domarrow-up-right. It's strongly recommended to use jest-dom because the error messages you get with it are much better.

    Advice: install and use @testing-library/jest-domarrow-up-right**

    hashtag
    Wrapping things in act unnecessarilyarrow-up-right

    Importance: medium

    I see people wrapping things in act like this because they see these "act" warnings all the time and are just desperately trying anything they can to get them to go away, but what they don't know is that render and fireEvent are already wrapped in act! So those are doing nothing useful.

    Most of the time, if you're seeing an act warning, it's not just something to be silenced, but it's actually telling you that something unexpected is happening in your test. You can learn more about this from my blog post (and videos): Fix the "not wrapped in act(...)" warningarrow-up-right.

    Advice: Learn when act is necessary and don't wrap things in act unnecessarily.

    hashtag
    Using the wrong queryarrow-up-right

    Importance: high

    We maintain a page called "Which query should I use?"arrow-up-right of the queries you should attempt to use in the order you should attempt to use them. If your goal is aligned with ours of having tests that give you confidence that your app will work when your users use them, then you'll want to query the DOM as closely to the way your end-users do so as possible. The queries we provide will help you to do this, but not all queries are created equally.

    hashtag
    Using container to query for elementsarrow-up-right

    As a sub-section of "Using the wrong query" I want to talk about querying on the container directly.

    We want to ensure that your users can interact with your UI and if you query around using querySelector we lose a lot of that confidence, the test is harder to read, and it will break more frequently. This goes hand-in-hand with the next sub-section:

    hashtag
    Not querying by textarrow-up-right

    As a sub-section of "Using the wrong query", I want to talk about why I recommend you query by the actual text (in the case of localization, I recommend the default locale), rather than using test IDs or other mechanisms everywhere.

    If you don't query by the actual text, then you have to do extra work to make sure that your translations are getting applied correctly. The biggest complaint I hear about this is that it leads to content writers breaking your tests. My rebuttal to that is that first, if a content writer changes "Username" to "Email" that's a change I definitely want to know about (because I'll need to change my implementation). Also, if there is a situation where they break something, fixing that issue takes no time at all. It's easy to triage and easy to fix.

    So the cost is pretty low, and the benefit is you get increased confidence that your translations are applied correctly and your tests are easier to write and read.

    I should mention that not everyone agrees with me on this, feel free to read more about it in this tweet threadarrow-up-right.

    hashtag
    Not using *ByRole most of the timearrow-up-right

    As a sub-section of "Using the wrong query" I want to talk about *ByRole. In recent versions, the *ByRole queries have been seriously improved (primarily thanks to great work by Sebastian Silbermannarrow-up-right) and are now the number one recommended approach to query your component's output. Here are some of my favorite features.

    The name option allows you to query elements by their "Accessible Name"arrow-up-right which is what screen readers will read for the element and it works even if your element has its text content split up by different elements. For example:

    One reason people don't use *ByRole queries is because they're not familiar with the implicit roles placed on elements. Here's a list of Roles on MDNarrow-up-right. So another one of my favorite features of the *ByRole queries is that if we're unable to find an element with the role you've specified, not only will we log the entire DOM to you like we do with normal get* or find* variants, but we also log all the available roles you can query by!

    This will fail with the following error message:

    Notice that we didn't have to add the role=button to our button for it to have the role of button. That's an implicit role, which leads us perfectly into our next one...

    Advice: Read and follow the recommendations The "Which Query Should I Use" Guidearrow-up-right.**

    hashtag
    Adding aria-, role, and other accessibility attributes incorrectlyarrow-up-right

    Importance: high

    Slapping accessibility attributes willy nilly is not only unnecessary (as in the case above), but it can also confuse screen readers and their users. The accessibility attributes should really only be used when semantic HTML doesn't satisfy your use case (like if you're building a non-native UI that you want to make accessible like an autocompletearrow-up-right). If that's what you're building, be sure to use an existing library that does this accessibly or follow the WAI-ARIA practices. They often have great examplesarrow-up-right.

    Note: to make inputs accessible via a "role" you'll want to specify the type attribute!

    Advice: Avoid adding unnecessary or incorrect accessibility attributes.

    hashtag
    Not using @testing-library/user-eventarrow-up-right

    Importance: medium

    @testing-library/user-eventarrow-up-right is a package that's built on top of fireEvent, but it provides several methods that resemble the user interactions more closely. In the example above, fireEvent.change will simply trigger a single change event on the input. However the type call, will trigger keyDown, keyPress, and keyUp events for each character as well. It's much closer to the user's actual interactions. This has the benefit of working well with libraries that you may use which don't actually listen for the change event.

    We're still working on @testing-library/user-event to ensure that it delivers what it promises: firing all the same events the user would fire when performing a specific action. I don't think we're quite there yet and this is why it's not baked-into @testing-library/dom (though it may be at some point in the future). However, I'm confident enough in it to recommend you give it a look and use it's utilities over fireEvent.

    Advice: Use @testing-library/user-event over fireEvent where possible.

    hashtag
    Using query* variants for anything except checking for non-existencearrow-up-right

    Importance: high

    The only reason the query* variant of the queries is exposed is for you to have a function you can call which does not throw an error if no element is found to match the query (it returns null if no element is found). The only reason this is useful is to verify that an element is not rendered to the page. The reason this is so important is because the get* and find* variants will throw an extremely helpful error if no element is found–it prints out the whole document so you can see what's rendered and maybe why your query failed to find what you were looking for. Whereas query* will only return null and the best toBeInTheDocument can do is say: "null isn't in the document" which is not very helpful.

    Advice: Only use the query* variants for asserting that an element cannot be found.

    hashtag
    Using waitFor to wait for elements that can be queried with find*arrow-up-right

    Importance: high

    Those two bits of code are basically equivalent (find* queries use waitFor under the hood), but the second is simpler and the error message you get will be better.

    Advice: use find* any time you want to query for something that may not be available right away.

    hashtag
    Passing an empty callback to waitForarrow-up-right

    Importance: high

    The purpose of waitFor is to allow you to wait for a specific thing to happen. If you pass an empty callback it might work today because all you need to wait for is "one tick of the event loop" thanks to the way your mocks work. But you'll be left with a fragile test which could easily fail if you refactor your async logic.

    Advice: wait for a specific assertion inside waitFor.

    hashtag
    Having multiple assertions in a single waitFor callbackarrow-up-right

    Importance: low

    Let's say that for the example above, window.fetch was called twice. So the waitFor call will fail, however, we'll have to wait for the timeout before we see that test failure. By putting a single assertion in there, we can both wait for the UI to settle to the state we want to assert on, and also fail faster if one of the assertions do end up failing.

    Advice: only put one assertion in a callback.

    hashtag
    Performing side-effects in waitForarrow-up-right

    Importance: high

    waitFor is intended for things that have a non-deterministic amount of time between the action you performed and the assertion passing. Because of this, the callback can be called (or checked for errors) a non-deterministic number of times and frequency (it's called both on an interval as well as when there are DOM mutations). So this means that your side-effect could run multiple times!

    This also means that you can't use snapshot assertions within waitFor. If you do want to use a snapshot assertion, then first wait for a specific assertion, and then after that you can take your snapshot.

    Advice: put side-effects outside waitFor callbacks and reserve the callback for assertions only.

    hashtag
    Using get* variants as assertionsarrow-up-right

    Importance: low

    This one's not really a big deal actually, but I thought I'd mention it and give my opinion on it. If get* queries are unsuccessful in finding the element, they'll throw a really helpful error message that shows you the full DOM structure (with syntax highlighting) which will help you during debugging. Because of this, the assertion could never possibly fail (because the query will throw before the assertion has a chance to).

    For this reason, many people skip the assertion. This really is fine honestly, but I personally normally keep the assertion in there just to communicate to readers of the code that it's not just an old query hanging around after a refactor but that I'm explicitly asserting that it exists.

    Advice: If you want to assert that something exists, make that assertion explicit.

    Not using Testing Library ESLint pluginsarrow-up-right
    // for/htmlFor relationship between label and form element id
    <label for="username-input">Username</label>
    <input id="username-input" />
    
    // The aria-labelledby attribute with form elements
    <label id="username-label">Username</label>
    <input aria-labelledby="username-label" />
    
    // Wrapper labels
    <label>Username <input /></label>
    
    // Wrapper labels where the label text is in another child element
    <label>
      <span>Username</span>
      <input />
    </label>
    
    // aria-label attributes
    // Take care because this is not a label that users can see on the page,
    // so the purpose of your input must be obvious to visual users.
    <input aria-label="Username" />
    import { screen } from "@testing-library/dom";
    
    const inputNode = screen.getByLabelText("Username");
    import { render, screen } from "@testing-library/react";
    
    render(<Login />);
    
    const inputNode = screen.getByLabelText("Username");
    cy.findByLabelText("Username").should("exist");
    // Multiple elements labelled via aria-labelledby
    <label id="username">Username</label>
    <input aria-labelledby="username" />
    <span aria-labelledby="username">Please enter your username</span>
    
    // Multiple labels with the same text
    <label>
      Username
      <input />
    </label>
    <label>
      Username
      <textarea></textarea>
    </label>
    const inputNode = screen.getByLabelText("Username", { selector: "input" });
    // This case is not valid
    // for/htmlFor between label and an element that is not a form element
    <section id="photos-section">
      <label for="photos-section">Photos</label>
    </section>
    import { screen } from "@testing-library/dom";
    
    const deleteElement = screen.getByTitle("Delete");
    const closeElement = screen.getByTitle("Close");
    import { render, screen } from "@testing-library/react";
    
    render(<MyComponent />);
    const deleteElement = screen.getByTitle("Delete");
    const closeElement = screen.getByTitle("Close");
    cy.findByTitle("Delete").should("exist");
    cy.findByTitle("Close").should("exist");
    // ❌
    const wrapper = render(<Example prop="1" />)
    wrapper.rerender(<Example prop="2" />)
    
    // ✅
    const {rerender} = render(<Example prop="1" />)
    rerender(<Example prop="2" />)
    // ❌
    import {render, screen, cleanup} from '@testing-library/react'
    
    afterEach(cleanup)
    
    // ✅
    import {render, screen} from '@testing-library/react'
    // ❌
    const {getByRole} = render(<Example />)
    const errorMessageNode = getByRole('alert')
    
    // ✅
    render(<Example />)
    const errorMessageNode = screen.getByRole('alert')
    import {render, screen} from '@testing-library/react'
    const button = screen.getByRole('button', {name: /disabled button/i})
    
    // ❌
    expect(button.disabled).toBe(true)
    // error message:
    //  expect(received).toBe(expected) // Object.is equality
    //
    //  Expected: true
    //  Received: false
    
    // ✅
    expect(button).toBeDisabled()
    // error message:
    //   Received element is not disabled:
    //     <button />
    // ❌
    act(() => {
      render(<Example />)
    })
    
    const input = screen.getByRole('textbox', {name: /choose a fruit/i})
    act(() => {
      fireEvent.keyDown(input, {key: 'ArrowDown'})
    })
    
    // ✅
    render(<Example />)
    const input = screen.getByRole('textbox', {name: /choose a fruit/i})
    fireEvent.keyDown(input, {key: 'ArrowDown'})
    // ❌
    // assuming you've got this DOM to work with:
    // <label>Username</label><input data-testid="username" />
    screen.getByTestId('username')
    
    // ✅
    // change the DOM to be accessible by associating the label and setting the type
    // <label for="username">Username</label><input id="username" type="text" />
    screen.getByRole('textbox', {name: /username/i})
    // ❌
    const {container} = render(<Example />)
    const button = container.querySelector('.btn-primary')
    expect(button).toHaveTextContent(/click me/i)
    
    // ✅
    render(<Example />)
    screen.getByRole('button', {name: /click me/i})
    // ❌
    screen.getByTestId('submit-button')
    
    // ✅
    screen.getByRole('button', {name: /submit/i})
    // assuming we've got this DOM structure to work with
    // <button><span>Hello</span> <span>World</span></button>
    
    screen.getByText(/hello world/i)
    // ❌ fails with the following error:
    // Unable to find an element with the text: /hello world/i. This could be
    // because the text is broken up by multiple elements. In this case, you can
    // provide a function for your text matcher to make your matcher more flexible.
    
    screen.getByRole('button', {name: /hello world/i})
    // ✅ works!
    // assuming we've got this DOM structure to work with
    // <button><span>Hello</span> <span>World</span></button>
    screen.getByRole('blah')
    TestingLibraryElementError: Unable to find an accessible element with the role "blah"
    
    Here are the accessible roles:
    
      button:
    
      Name "Hello World":
      <button />
    
      --------------------------------------------------
    
    <body>
      <div>
        <button>
          <span>
            Hello
          </span>
    
          <span>
            World
          </span>
        </button>
      </div>
    </body>
    // ❌
    render(<button role="button">Click me</button>)
    
    // ✅
    render(<button>Click me</button>)
    // ❌
    fireEvent.change(input, {target: {value: 'hello world'}})
    
    // ✅
    userEvent.type(input, 'hello world')
    // ❌
    expect(screen.queryByRole('alert')).toBeInTheDocument()
    
    // ✅
    expect(screen.getByRole('alert')).toBeInTheDocument()
    expect(screen.queryByRole('alert')).not.toBeInTheDocument()
    // ❌
    const submitButton = await waitFor(() =>
      screen.getByRole('button', {name: /submit/i}),
    )
    
    // ✅
    const submitButton = await screen.findByRole('button', {name: /submit/i})
    // ❌
    await waitFor(() => {})
    expect(window.fetch).toHaveBeenCalledWith('foo')
    expect(window.fetch).toHaveBeenCalledTimes(1)
    
    // ✅
    await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
    expect(window.fetch).toHaveBeenCalledTimes(1)
    // ❌
    await waitFor(() => {
      expect(window.fetch).toHaveBeenCalledWith('foo')
      expect(window.fetch).toHaveBeenCalledTimes(1)
    })
    
    // ✅
    await waitFor(() => expect(window.fetch).toHaveBeenCalledWith('foo'))
    expect(window.fetch).toHaveBeenCalledTimes(1)
    // ❌
    await waitFor(() => {
      fireEvent.keyDown(input, {key: 'ArrowDown'})
      expect(screen.getAllByRole('listitem')).toHaveLength(3)
    })
    
    // ✅
    fireEvent.keyDown(input, {key: 'ArrowDown'})
    await waitFor(() => {
      expect(screen.getAllByRole('listitem')).toHaveLength(3)
    })
    // ❌
    screen.getByRole('alert', {name: /error/i})
    
    // ✅
    expect(screen.getByRole('alert', {name: /error/i})).toBeInTheDocument()
    https://app.abstract.com/projects/7d33aa49-f1f0-47eb-971d-893d6457bcbcapp.abstract.comchevron-right

    ByPlaceholderText

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    getByPlaceholderText, queryByPlaceholderText, getAllByPlaceholderText, queryAllByPlaceholderText, findByPlaceholderText, findAllByPlaceholderText

    hashtag
    API

    getByPlaceholderText(
      // If you're using `screen`, then skip the container argument:
      container: HTMLElement,
      text: TextMatch,
      options?: {
        exact?: boolean = true,
        normalizer?: NormalizerFn,
      }): HTMLElement

    This will search for all elements with a placeholder attribute and find one that matches the given TextMatch.

    <Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { label: 'Cypress', value: 'cypress', }, ] }>

    Note

    A placeholder is not a good substitute for a label so you should generally use getByLabelText instead.

    hashtag
    Options

    TextMatch options

    Migrate from Enzyme

    This page is intended for developers who have experience with Enzyme and are trying to understand how to migrate to React Testing Library. It does not go into great detail about how to migrate all types of tests, but it does have some helpful information for those who are comparing Enzyme with React Testing Library.

    hashtag
    What is React Testing Library?

    React Testing Library is part of an open-source project named Testing Libraryarrow-up-right. There are several other helpful tools and libraries in the Testing Library project which you can use to write more concise and useful tests. Besides React Testing Library, here are some of the project's other libraries that can help you along the way:

    • : jest-dom provides a set of custom Jest matchers that you can use to extend Jest. These make your tests more declarative, clearer to read, and easier to maintain.

    • : user-event tries to simulate the real events that happen in the browser as the user interacts with elements on the page. For example, userEvent.click(checkbox) would change the state of the checkbox.

    hashtag
    Why should I use React Testing Library?

    Enzyme is a powerful test library, and its contributors did a lot for the JavaScript community. In fact, many of the React Testing Library maintainers used and contributed to Enzyme for years before developing and working on React Testing Library. So we want to say thank you to the contributors of Enzyme!

    The primary purpose of React Testing Library is to increase confidence in your tests by testing your components in the way a user would use them. Users don't care what happens behind the scenes, they just see and interact with the output. Instead of accessing the components' internal APIs or evaluating their state, you'll get more confidence by writing your tests based on the component output.

    React Testing Library aims to solve the problem that many developers face when writing tests with Enzyme, which allows (and encourages) developers to test . Tests which do this ultimately prevent you from modifying and refactoring the component without changing its tests. As a result, the tests slow down development speed and productivity. Every small change may require rewriting some part of your tests, even if the change does not affect the component's output.

    Rewriting your tests in React Testing library is worthwhile because you'll be trading tests that slow you down for tests that give you more confidence and increase your productivity in the long run.

    hashtag
    How to migrate from Enzyme to React Testing Library?

    To ensure a successful migration, we recommend doing it incrementally by running the two test libraries side by side in the same application, porting your Enzyme tests to React Testing Library one by one. That makes it possible to migrate even large and complex applications without disrupting other business because the work can be done collaboratively and spread out over time.

    hashtag
    Install React Testing Library

    First, install React Testing Library and the jest-dom helper library (you can check this page for the complete installation and setup guide).

    hashtag
    Import React Testing Library to your test

    If you're using Jest (you can use other test frameworks), then you only need to import the following modules into your test file:

    The test structure can be the same as you would write with Enzyme:

    Note: you can also use describe and it blocks with React Testing Library. React Testing Library doesn't replace Jest, just Enzyme. We recommend test because it helps with this: .

    hashtag
    Basic Enzyme to React Testing Library migration examples

    One thing to keep in mind is that there's not a one-to-one mapping of Enzyme features to React Testing Library features. Many Enzyme features result in inefficient tests anyway, so some of the features you're accustomed to with Enzyme need to be left behind (no more need for a wrapper variable or wrapper.update() calls, etc.).

    React Testing Library has helpful queries which let you access your component's elements and their properties. We'll show some typical Enzyme tests along with alternatives using React Testing Library.

    Let's say we have a Welcome component which shows a welcome message. We will have a look at both Enzyme and React Testing Library tests to learn how we can test this component:

    React Component

    The following component gets a name from props and shows a welcome message in an h1 element. It also has a text input which users can change to a different name, and the template updates accordingly. Check the live version on .

    hashtag
    Test 1: Render the component, and check if the h1 value is correct

    Enzyme test

    React Testing library

    As you can see, the tests are pretty similar. Enzyme's shallow renderer doesn't render sub-components, so React Testing Library's render method is more similar to Enzyme's mount method.

    In React Testing Library, you don't need to assign the render result to a variable (i.e. wrapper). You can simply access the rendered output by calling functions on the screen object. The other good thing to know is that React Testing Library automatically cleans up the environment after each test so you don't need to call cleanup in an afterEach or beforeEach function.

    The other thing that you might notice is getByRole which has 'heading' as its argument. 'heading' is the accessible role of the h1 element. You can learn more about them on the queries documentation page. One of the things people quickly learn to love about Testing Library is how it encourages you to write more accessible applications (because if it's not accessible, then it's harder to test).

    hashtag
    Test 2: Input texts must have correct value

    In the component above, the input values are initialized with the props.firstName and props.lastName values. We need to check whether the value is correct or not.

    Enzyme

    React Testing Library

    Cool! It's pretty simple and handy, and the tests are clear enough that we don't need to talk much about them. Something that you might notice is that the <form> has a role="form" attribute, but what is it?

    role is one of the accessibility-related attributes that is recommended to use to improve your web application for people with disabilities. Some elements have default role values and you don't need to set one for them, but some others like <div> do not have default role values. You can use different approaches to access the <div> element, but we recommend trying to access elements by their implicit role to make sure your component is accessible by people with disabilities and those using screen readers. This section of the query documentation might help you understand the concepts better.

    A <form> element must have a name attribute in order to have an implicit role of 'form' (as required by the specification).

    React Testing Library aims to test the components how users use them. Users see buttons, headings, forms and other elements by their role, not by their id, class, or element tag name. Therefore, when you use React Testing Library you should avoid accessing the DOM with the document.querySelector API. (You can use it in your tests, but it's not recommended for the reasons stated in this paragraph.)

    React Testing Library exposes some handy query APIs which help you access the component elements efficiently. You can see the list of available queries here. If you're not sure which query you should use in a given situation, we have a great page which explains which query to use, so check it out!

    If you still have a question about which of React Testing Library's queries to use, then check out and the accompanying Chrome extension which aims to enable developers to find the best query when writing tests. It also helps you find the best queries to select elements. It allows you to inspect the element hierarchies in the Chrome Developer Tools and provides you with suggestions on how to select them, all while encouraging good testing practices.

    hashtag
    Using act() and wrapper.update()

    When testing asynchronous code in Enzyme, you usually need to call act() to run your tests correctly. When using React Testing Library, you don't need to explicitly call act() most of the time because it wraps API calls with act() by default.

    update() syncs the Enzyme component tree snapshot with the React component tree, so you may see wrapper.update() in Enzyme tests. React Testing Library does not have (or need) a similar method, which is good for you since you need to handle fewer things!

    hashtag
    Simulate user events

    There are two ways to simulate user events with React Testing Library. One way is to use the user-event library, and the other way is to use fireEvent which is included in React Testing Library. user-event is actually built on top of fireEvent (which simply calls dispatchEvent on the given element). user-event is generally recommended because it ensures that all the events are fired in the correct order for typical user interactions. This helps ensure your tests resemble the way your software is actually used.

    To use the @testing-library/user-event module, first install it:

    Now you can import it into your test:

    To demonstrate how to use the user-event library, imagine we have a Checkbox component which shows a checkbox input and an associated label. We want to simulate the event of a user clicking the checkbox:

    We want to test that when a user clicks on the checkbox's associated label, the input's "checked" property is properly set. Let's see how we might write a test for that case:

    Nice!

    hashtag
    Triggering class methods in tests (wrapper.instance())

    As we already discussed, we recommend against testing implementation details and things that users will not be aware of. We aim to test and interact with the component more like how our users would.

    If your test uses `instance()` or `state()`, know that you're testing things that the user couldn't possibly know about or even care about, which will take your tests further from giving you confidence that things will work when your user uses them. —

    If you're unsure how to test something internal to your component, then take a step back and consider: "What would the user do to trigger this code to run?" Then make your test do that.

    hashtag
    How to shallow render a component?

    In general, you should avoid mocking out components. However, if you need to, then try using . For more information, see the FAQ.

    ByRole

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    getByRole, queryByRole, getAllByRole, queryAllByRole, findByRole, findAllByRole

    hashtag
    API

    Queries for elements with the given role (and it also accepts a TextMatch

    Setup

    import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'

    React Testing Library does not require any configuration to be used. However, there are some things you can do when configuring your testing framework to reduce some boilerplate. In these docs we'll demonstrate configuring Jest, but you should be able to do similar things with (React Testing Library does not require that you use Jest).

    hashtag
    Global Config

    Gitbook Action Build
    https://dewap.duke-energy.com/EHSTrain/HS1010_WS/index_lms.html?aicc_sid=AICChwSV3HVHAqqYgW73-UGijiVHXrT-ZrNFx8YJQvzf5jI&aicc_url=https%3A%2F%2Fduke-energy.csod.com%2FLMS%2Fscorm%2Faicc.aspxdewap.duke-energy.comchevron-right

    Testing

    https: //testing-playground.com/gist/7c68f0970c4ad5e2e1342390f4dea70c/64c2420ce9169f440cb8810b7ce438f53c766cef

    • [A clear way to unit testing React JS components using Jest and React Testing Library](https: //www.richardkotze.com/coding/react-testing-library-jest) by [Richard Kotze](https: //github.com/rkotze)

    • [Integration testing in React](https: //medium.com/@jeffreyrussom/integration-testing-in-react-21f92a55a894) by [Jeffrey Russom](https: //github.com/qswitcher)

    • [React Testing Library have fantastic testing 🐐](https: //medium.com/yazanaabed/react-testing-library-have-a-fantastic-testing-198b04699237) by [Yazan Aabed](https: //github.com/YazanAabeed)

    • [Building a React Tooltip Library](https: //www.youtube.com/playlist?list=PLMV09mSPNaQmFLPyrfFtpUdClVfutjF5G) by [divyanshu013](https: //github.com/divyanshu013) and [metagrover](https: //github.com/metagrover)

    • [A sample repo using React Testing Library to test a Relay Modern GraphQL app](https: //github.com/zth/relay-modern-flow-jest-example)

    • [Creating Readable Tests Using React Testing Library](https: //medium.com/flatiron-labs/creating-readable-tests-using-react-testing-library-2bd03c49c284) by [Lukeghenco](https: //github.com/Lukeghenco)

    • [My Experience moving from Enzyme to React Testing Library](https: //medium.com/@boyney123/my-experience-moving-from-enzyme-to-react-testing-library-5ac65d992ce) by [David Boyne](https: //github.com/boyney123)

    • [Testing Formik with React Testing Library](https: //scottsauber.com/2019/05/25/testing-formik-with-react-testing-library/) by [Scott Sauber](https: //github.com/scottsauber)

    • [How to Test Asynchronous Methods](https: //www.polvara.me/posts/how-to-test-asynchronous-methods/) by [Gpx](https: //twitter.com/Gpx)

    • [Writing better tests with react-testing-library](https: //www.youtube.com/watch?v=O0VxvRqgm7g) by [Billy Mathews](https: //twitter.com/BillRMathews)

    • [React Hooks broke my tests, now what?](https: //youtu.be/p3WS9GmfX_Q) by [Daniel Afonso](https: //twitter.com/danieljcafonso)

    • [Testing Apollo Components Using react-testing-library](https: //www.arahansen.com/testing-apollo-components-using-react-testing-library/) by [Andrew Hansen](https: //twitter.com/arahansen)

    <input placeholder="Username" />
    import { screen } from "@testing-library/dom";
    
    const inputNode = screen.getByPlaceholderText("Username");
    @testing-library/jest-domarrow-up-right
    @testing-library/user-eventarrow-up-right
    implementation detailsarrow-up-right
    Avoid Nesting When You're Testingarrow-up-right
    CodeSandboxarrow-up-right
    testing-playground.comarrow-up-right
    Testing Playgroundarrow-up-right
    Kent C. Doddsarrow-up-right
    Jest's mocking featurearrow-up-right
    ). Default roles are taken into consideration e.g.
    <button />
    has the
    button
    role without explicitly setting the
    role
    attribute. Here you can see
    .

    Please note that setting a role and/or aria-* attribute that matches the implicit ARIA semantics is unnecessary and is not recommended as these properties are already set by the browser, and we must not use the role and aria-* attributes in a manner that conflicts with the semantics described. For example, a button element can't have the role attribute of heading, because the button element has default characteristics that conflict with the heading role.

    Roles are matched literally by string equality, without inheriting from the ARIA role hierarchy. As a result, querying a superclass role like checkbox will not include elements with a subclass role like switch.

    You can query the returned element(s) by their accessible name or descriptionarrow-up-right. The accessible name is for simple cases equal to e.g. the label of a form element, or the text content of a button, or the value of the aria-label attribute. It can be used to query a specific element if multiple elements with the same role are present on the rendered content. For an in-depth guide check out "What is an accessible name?" from ThePacielloGrouparrow-up-right. If you only query for a single element with getByText('The name') it's oftentimes better to use getByRole(expectedRole, { name: 'The name' }). The accessible name query does not replace other queries such as *ByAlt or *ByTitle. While the accessible name can be equal to these attributes, it does not replace the functionality of these attributes. For example <img aria-label="fancy image" src="fancy.jpg" /> will be returned for both getByAltText('fancy image') and getByRole('img', { name: 'fancy image' }). However, the image will not display its description if fancy.jpg could not be loaded. Whether you want to assert this functionality in your test or not is up to you.

    hashtag
    Options

    hashtag
    hidden

    If you set hidden to true elements that are normally excluded from the accessibility tree are considered for the query as well. The default behavior follows https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion with the exception of role="none" and role="presentation" which are considered in the query in any case. For example in

    getByRole('button') would only return the Close dialog-button. To make assertions about the Open dialog-button you would need to use getAllByRole('button', { hidden: true }).

    The default value for hidden can be configured.

    hashtag
    selected

    You can filter the returned elements by their selected state by setting selected: true or selected: false.

    For example in

    you can get the "Native"-tab by calling getByRole('tab', { selected: true }). To learn more about the selected state and which elements can have this state see ARIA aria-selectedarrow-up-right.

    hashtag
    checked

    You can filter the returned elements by their checked state by setting checked: true or checked: false.

    For example in

    you can get the "Sugar" option by calling getByRole('checkbox', { checked: true }). To learn more about the checked state and which elements can have this state see ARIA aria-checkedarrow-up-right.

    Note

    Checkboxes have a "mixed" state, which is considered neither checked nor unchecked (details herearrow-up-right).

    hashtag
    current

    You can filter the returned elements by their current state by setting current: boolean | string. Note that no aria-current attribute will match current: false since false is the default value for aria-current.

    For example in

    you can get the "👍" link by calling getByRole('link', { current: true }) and the "👎" by calling getByRole('link', { current: false }). To learn more about the current state see ARIA aria-currentarrow-up-right.

    hashtag
    pressed

    Buttons can have a pressed state. You can filter the returned elements by their pressed state by setting pressed: true or pressed: false.

    For example in

    you can get the "👍" button by calling getByRole('button', { pressed: true }). To learn more about the pressed state see ARIA aria-pressedarrow-up-right.

    hashtag
    expanded

    You can filter the returned elements by their expanded state by setting expanded: true or expanded: false.

    For example in

    you can get the "Expandable Menu Item" link by calling getByRole('link', { expanded: false }). To learn more about the expanded state and which elements can have this state see ARIA aria-expandedarrow-up-right.

    <Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { label: 'Cypress', value: 'cypress', }, ] }>

    hashtag
    queryFallbacks

    By default, it's assumed that the first role of each element is supported, so only the first role can be queried. If you need to query an element by any of its fallback roles instead, you can use queryFallbacks: true.

    For example, getByRole('switch') would always match <div role="switch checkbox" /> because it's the first role, while getByRole('checkbox') would not. However, getByRole('checkbox', { queryFallbacks: true }) would enable all fallback roles and therefore match the same element.

    An element doesn't have multiple roles in a given environment. It has a single one. Multiple roles in the attribute are evaluated from left to right until the environment finds the first role it understands. This is useful when new roles get introduced and you want to start supporting those as well as older environments that don't understand that role (yet).

    hashtag
    level

    An element with the heading role can be queried by any heading level getByRole('heading') or by a specific heading level using the level option getByRole('heading', { level: 2 }).

    The level option queries the element(s) with the heading role matching the indicated level determined by the semantic HTML heading elements <h1>-<h6> or matching the aria-level attribute.

    Given the example below,

    you can query the Heading Level Three heading using getByRole('heading', { level: 3 }).

    While it is possible to explicitly set role="heading" and aria-level attribute on an element, it is strongly encouraged to use the semantic HTML headings <h1>-<h6>.

    To learn more about the aria-level property, see ARIA aria-levelarrow-up-right.

    The level option is only applicable to the heading role. An error will be thrown when used with any other role.

    hashtag
    description

    You can filter the returned elements by their accessible descriptionarrow-up-right for those cases where you have several elements with the same role and they don't have an accessible name but they do have a description. This would be the case for elements with alertdialogarrow-up-right role, where the aria-describedby attribute is used to describe the element's content.

    For example in

    You can query a specific element like this

    a table of HTML elements with their default and desired rolesarrow-up-right
    Adding options to your global test config can simplify the setup and teardown of tests in individual files.

    hashtag
    Custom Render

    It's often useful to define a custom render method that includes things like global context providers, data stores, etc. To make this available globally, one approach is to define a utility file that re-exports everything from React Testing Library. You can replace React Testing Library with this file in all your imports. See belowarrow-up-right for a way to make your test util file accessible without using relative paths.

    The example below sets up data providers using the wrapper option to render.

    <Tabs groupId="test-utils" defaultValue="jsx" values={[ {label: 'Javascript', value: 'jsx'}, {label: 'Typescript', value: 'tsx'}, ]}>

    Note

    Babel versions lower than 7 throw an error when trying to override the named export in the example above. See #169arrow-up-right and the workaround below.

    chevron-rightWorkaround for Babel 6hashtag

    You can use CommonJS modules instead of ES modules, which should work in Node:

    hashtag
    Add custom queries

    Note

    Generally you should not need to create custom queries for react-testing-library. Where you do use it, you should consider whether your new queries encourage you to test in a user-centric way, without testing implementation details.

    You can define your own custom queries as described in the Custom Queries documentation, or via the buildQueries helper. Then you can use them in any render call using the queries option. To make the custom queries available globally, you can add them to your custom render method as shown below.

    In the example below, a new set of query variants are created for getting elements by data-cy, a "test ID" convention mentioned in the Cypress.ioarrow-up-right documentation.

    You can then override and append the new queries via the render function by passing a queries option.

    If you want to add custom queries globally, you can do this by defining a custom render method:

    <Tabs groupId="test-utils" defaultValue="jsx" values={[ {label: 'Javascript', value: 'jsx'}, {label: 'Typescript', value: 'tsx'}, ]}>

    You can then use your custom queries as you would any other query:

    hashtag
    Configuring Jest with Test Utils

    To make your custom test file accessible in your Jest test files without using relative imports (../../test-utils), add the folder containing the file to the Jest moduleDirectories option.

    This will make all the .js files in the test-utils directory importable without ../.

    If you're using TypeScript, merge this into your tsconfig.json. If you're using Create React App without TypeScript, save this to jsconfig.json instead.

    hashtag
    Jest 28

    If you're using Jest 28 or later, jest-environment-jsdom package now must be installed separately.

    jsdom is also no longer the default environment. You can enable jsdom globally by editing jest.config.js:

    Or if you only need jsdom in some of your tests, you can enable it as and when needed using docblocksarrow-up-right:

    hashtag
    Jest 27

    If you're using a recent version of Jest (27), jsdom is no longer the default environment. You can enable jsdom globally by editing jest.config.js:

    Or if you only need jsdom in some of your tests, you can enable it as and when needed using docblocksarrow-up-right:

    hashtag
    Jest 24 (or lower) and defaults

    If you're using the Jest testing framework version 24 or lower with the default configuration, it's recommended to use jest-environment-jsdom-fifteen package as Jest uses a version of the jsdom environment that misses some features and fixes, required by React Testing Library.

    First, install jest-environment-jsdom-fifteen.

    Then specify jest-environment-jsdom-fifteen as the testEnvironment:

    hashtag
    Using without Jest

    If you're running your tests in the browser bundled with webpack (or similar) then React Testing Library should work out of the box for you. However, most people using React Testing Library are using it with the Jest testing framework with the testEnvironment set to jest-environment-jsdom (which is the default configuration with Jest 26 and earlier).

    jsdom is a pure JavaScript implementation of the DOM and browser APIs that runs in Node. If you're not using Jest and you would like to run your tests in Node, then you must install jsdom yourself. There's also a package called global-jsdom which can be used to setup the global environment to simulate the browser APIs.

    First, install jsdom and global-jsdom.

    With mocha, the test command would look something like this:

    hashtag
    Skipping Auto Cleanup

    Cleanup is called after each test automatically by default if the testing framework you're using supports the afterEach global (like mocha, Jest, and Jasmine). However, you may choose to skip the auto cleanup by setting the RTL_SKIP_AUTO_CLEANUP env variable to 'true'. You can do this with cross-envarrow-up-right like so:

    To make this even easier, you can also simply import @testing-library/react/dont-cleanup-after-each which will do the same thing. Just make sure you do this before importing @testing-library/react. You could do this with Jest's setupFiles configuration:

    Or with mocha's -r flag:

    Alternatively, you could import @testing-library/react/pure in all your tests that you don't want the cleanup to run and the afterEach won't be setup automatically.

    hashtag
    Auto Cleanup in Mocha's watch mode

    When using Mocha in watch mode, the globally registered cleanup is run only the first time after each test. Therefore, subsequent runs will most likely fail with a TestingLibraryElementError: Found multiple elements error.

    To enable automatic cleanup in Mocha's watch mode, add a cleanup root hookarrow-up-right. Create a mocha-watch-cleanup-after-each.js file with the following contents:

    And register it using mocha's -r flag:

    any testing frameworkarrow-up-right
    import { render, screen } from "@testing-library/react";
    
    render(<MyComponent />);
    const inputNode = screen.getByPlaceholderText("Username");
    cy.findByPlaceholderText("Username").should("exist");
    npm install --save-dev @testing-library/react @testing-library/jest-dom
    // import React so you can use JSX (React.createElement) in your test
    import React from "react";
    
    /**
     * render: lets us render the component as React would
     * screen: a utility for finding elements the same way the user does
     */
    import { render, screen } from "@testing-library/react";
    test("test title", () => {
      // Your tests come here...
    });
    const Welcome = (props) => {
      const [values, setValues] = useState({
        firstName: props.firstName,
        lastName: props.lastName,
      });
    
      const handleChange = (event) => {
        setValues({ ...values, [event.target.name]: event.target.value });
      };
    
      return (
        <div>
          <h1>
            Welcome, {values.firstName} {values.lastName}
          </h1>
    
          <form name="userName">
            <label>
              First Name
              <input
                value={values.firstName}
                name="firstName"
                onChange={handleChange}
              />
            </label>
    
            <label>
              Last Name
              <input
                value={values.lastName}
                name="lastName"
                onChange={handleChange}
              />
            </label>
          </form>
        </div>
      );
    };
    
    export default Welcome;
    test("has correct welcome text", () => {
      const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />);
      expect(wrapper.find("h1").text()).toEqual("Welcome, John Doe");
    });
    test("has correct welcome text", () => {
      render(<Welcome firstName="John" lastName="Doe" />);
      expect(screen.getByRole("heading")).toHaveTextContent("Welcome, John Doe");
    });
    test("has correct input value", () => {
      const wrapper = shallow(<Welcome firstName="John" lastName="Doe" />);
      expect(wrapper.find('input[name="firstName"]').value).toEqual("John");
      expect(wrapper.find('input[name="lastName"]').value).toEqual("Doe");
    });
    test("has correct input value", () => {
      render(<Welcome firstName="John" lastName="Doe" />);
      expect(screen.getByRole("form")).toHaveFormValues({
        firstName: "John",
        lastName: "Doe",
      });
    });
    npm install --save-dev @testing-library/user-event @testing-library/dom
    import userEvent from "@testing-library/user-event";
    import React from "react";
    
    const Checkbox = () => {
      return (
        <div>
          <label htmlFor="checkbox">Check</label>
          <input id="checkbox" type="checkbox" />
        </div>
      );
    };
    
    export default Checkbox;
    test("handles click correctly", async () => {
      render(<Checkbox />);
      const user = userEvent.setup();
    
      // You can also call this method directly on userEvent,
      // but using the methods from `.setup()` is recommended.
      await user.click(screen.getByText("Check"));
    
      expect(screen.getByLabelText("Check")).toBeChecked();
    });
    getByRole(
      // If you're using `screen`, then skip the container argument:
      container: HTMLElement,
      role: TextMatch,
      options?: {
        exact?: boolean = true,
        hidden?: boolean = false,
        name?: TextMatch,
        description?: TextMatch,
        normalizer?: NormalizerFn,
        selected?: boolean,
        checked?: boolean,
        pressed?: boolean,
        current?: boolean | string,
        expanded?: boolean,
        queryFallbacks?: boolean,
        level?: number,
      }): HTMLElement
    <body>
      <main aria-hidden="true">
        <button>Open dialog</button>
      </main>
      <div role="dialog">
        <button>Close dialog</button>
      </div>
    </body>
    <body>
      <div role="tablist">
        <button role="tab" aria-selected="true">Native</button>
        <button role="tab" aria-selected="false">React</button>
        <button role="tab" aria-selected="false">Cypress</button>
      </div>
    </body>
    <body>
      <section>
        <button role="checkbox" aria-checked="true">Sugar</button>
        <button role="checkbox" aria-checked="false">Gummy bears</button>
        <button role="checkbox" aria-checked="false">Whipped cream</button>
      </section>
    </body>
    <body>
      <nav>
        <a href="current/page" aria-current="true">👍</a>
        <a href="another/page">👎</a>
      </nav>
    </body>
    <body>
      <section>
        <button aria-pressed="true">👍</button>
        <button aria-pressed="false">👎</button>
      </section>
    </body>
    <body>
      <nav>
        <ul>
          <li>
            <a aria-expanded="false" aria-haspopup="true" href="..."
              >Expandable Menu Item</a
            >
            <ul>
              <li><a href="#">Submenu Item 1</a></li>
              <li><a href="#">Submenu Item 1</a></li>
            </ul>
          </li>
          <li><a href="#">Regular Menu Item</a></li>
        </ul>
      </nav>
    </body>
    <div role="dialog">...</div>
    import { screen } from "@testing-library/dom";
    
    const dialogContainer = screen.getByRole("dialog");
    import { render, screen } from "@testing-library/react";
    
    render(<MyComponent />);
    const dialogContainer = screen.getByRole("dialog");
    cy.findByRole("dialog").should("exist");
    <body>
      <section>
        <h1>Heading Level One</h1>
        <h2>First Heading Level Two</h2>
        <h3>Heading Level Three</h3>
        <div role="heading" aria-level="2">Second Heading Level Two</div>
      </section>
    </body>
    getByRole("heading", { level: 1 });
    // <h1>Heading Level One</h1>
    
    getAllByRole("heading", { level: 2 });
    // [
    //   <h2>First Heading Level Two</h2>,
    //   <div role="heading" aria-level="2">Second Heading Level Two</div>
    // ]
    <body>
      <ul>
        <li role="alertdialog" aria-describedby="notification-id-1">
          <div><button>Close</button></div>
          <div id="notification-id-1">You have unread emails</div>
        </li>
        <li role="alertdialog" aria-describedby="notification-id-2">
          <div><button>Close</button></div>
          <div id="notification-id-2">Your session is about to expire</div>
        </li>
      </ul>
    </body>
    getByRole("alertdialog", { description: "Your session is about to expire" });
    const rtl = require("@testing-library/react");
    
    const customRender = (ui, options) =>
      rtl.render(ui, {
        myDefaultOption: "something",
        ...options,
      });
    
    module.exports = {
      ...rtl,
      render: customRender,
    };
    - import { render, fireEvent } from '@testing-library/react';
    + import { render, fireEvent } from '../test-utils';
    import React from "react";
    import { render } from "@testing-library/react";
    import { ThemeProvider } from "my-ui-lib";
    import { TranslationProvider } from "my-i18n-lib";
    import defaultStrings from "i18n/en-x-default";
    
    const AllTheProviders = ({ children }) => {
      return (
        <ThemeProvider theme="light">
          <TranslationProvider messages={defaultStrings}>
            {children}
          </TranslationProvider>
        </ThemeProvider>
      );
    };
    
    const customRender = (ui, options) =>
      render(ui, { wrapper: AllTheProviders, ...options });
    
    // re-export everything
    export * from "@testing-library/react";
    
    // override render method
    export { customRender as render };
    - import { render, fireEvent } from '@testing-library/react';
    + import { render, fireEvent } from '../test-utils';
    import React, { FC, ReactElement } from "react";
    import { render, RenderOptions } from "@testing-library/react";
    import { ThemeProvider } from "my-ui-lib";
    import { TranslationProvider } from "my-i18n-lib";
    import defaultStrings from "i18n/en-x-default";
    
    const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => {
      return (
        <ThemeProvider theme="light">
          <TranslationProvider messages={defaultStrings}>
            {children}
          </TranslationProvider>
        </ThemeProvider>
      );
    };
    
    const customRender = (
      ui: ReactElement,
      options?: Omit<RenderOptions, "wrapper">
    ) => render(ui, { wrapper: AllTheProviders, ...options });
    
    export * from "@testing-library/react";
    export { customRender as render };
    import { queryHelpers, buildQueries } from "@testing-library/react";
    
    // The queryAllByAttribute is a shortcut for attribute-based matchers
    // You can also use document.querySelector or a combination of existing
    // testing library utilities to find matching nodes for your query
    const queryAllByDataCy = (...args) =>
      queryHelpers.queryAllByAttribute("data-cy", ...args);
    
    const getMultipleError = (c, dataCyValue) =>
      `Found multiple elements with the data-cy attribute of: ${dataCyValue}`;
    const getMissingError = (c, dataCyValue) =>
      `Unable to find an element with the data-cy attribute of: ${dataCyValue}`;
    
    const [
      queryByDataCy,
      getAllByDataCy,
      getByDataCy,
      findAllByDataCy,
      findByDataCy,
    ] = buildQueries(queryAllByDataCy, getMultipleError, getMissingError);
    
    export {
      queryByDataCy,
      queryAllByDataCy,
      getByDataCy,
      getAllByDataCy,
      findAllByDataCy,
      findByDataCy,
    };
    import { render, queries } from "@testing-library/react";
    import * as customQueries from "./custom-queries";
    
    const customRender = (ui, options) =>
      render(ui, { queries: { ...queries, ...customQueries }, ...options });
    
    // re-export everything
    export * from "@testing-library/react";
    
    // override render method
    export { customRender as render };
    import { render, queries, RenderOptions } from "@testing-library/react";
    import * as customQueries from "./custom-queries";
    import { ReactElement } from "react";
    
    const customRender = (
      ui: ReactElement,
      options?: Omit<RenderOptions, "queries">
    ) => render(ui, { queries: { ...queries, ...customQueries }, ...options });
    
    export * from "@testing-library/react";
    export { customRender as render };
    const { getByDataCy } = render(<Component />);
    
    expect(getByDataCy("my-component")).toHaveTextContent("Hello");
    - import { render, fireEvent } from '../test-utils';
    + import { render, fireEvent } from 'test-utils';
    module.exports = {
      moduleDirectories: [
        'node_modules',
    +   // add the directory with the test-utils.js file, for example:
    +   'utils', // a utility folder
    +    __dirname, // the root directory
      ],
      // ... other options ...
    }
    {
      "compilerOptions": {
        "baseUrl": "src",
        "paths": {
          "test-utils": ["./utils/test-utils"]
        }
      }
    }
    npm install --save-dev jest-environment-jsdom
     module.exports = {
    +  testEnvironment: 'jsdom',
       // ... other options ...
     }
    /**
     * @jest-environment jsdom
     */
     module.exports = {
    +  testEnvironment: 'jest-environment-jsdom',
       // ... other options ...
     }
    /**
     * @jest-environment jsdom
     */
    npm install --save-dev jest-environment-jsdom-fifteen
     module.exports = {
    +  testEnvironment: 'jest-environment-jsdom-fifteen',
       // ... other options ...
     }
    npm install --save-dev jsdom global-jsdom
    mocha --require global-jsdom/register
    cross-env RTL_SKIP_AUTO_CLEANUP=true jest
    {
      // ... other jest config
      setupFiles: ["@testing-library/react/dont-cleanup-after-each"];
    }
    mocha -r @testing-library/react/dont-cleanup-after-each
    const { cleanup } = require("@testing-library/react");
    
    exports.mochaHooks = {
      afterEach() {
        cleanup();
      },
    };
    mocha -r ./mocha-watch-cleanup-after-each.js

    JEST

    hashtag
    The problem

    You want to use [jest][] to write tests that assert various things about the state of a DOM. As part of that goal, you want to avoid all the repetitive patterns that arise in doing so. Checking for an element's attributes, its text content, its css classes, you name it.

    hashtag
    This solution

    The @testing-library/jest-dom library provides a set of custom jest matchers that you can use to extend jest. These will make your tests more declarative, clear to read and to maintain.

    hashtag
    Table of Contents

    • Installation

    • Usage

      • With TypeScript

    hashtag
    Installation

    This module is distributed via [npm][npm] which is bundled with [node][node] and should be installed as one of your project's devDependencies:

    or

    for installation with package manager.

    Note: We also recommend installing the jest-dom eslint plugin which provides auto-fixable lint rules that prevent false positive tests and improve test readability by ensuring you are using the right matchers in your tests. More details can be found at .

    hashtag
    Usage

    Import @testing-library/jest-dom once (for instance in your ) and you're good to go:

    hashtag
    With TypeScript

    If you're using TypeScript, make sure your setup file is a .ts and not a .js to include the necessary types.

    You will also need to include your setup file in your tsconfig.json if you haven't already:

    hashtag
    Custom matchers

    @testing-library/jest-dom can work with any library or framework that returns DOM elements from queries. The custom matcher examples below are written using matchers from @testing-library's suite of libraries (e.g. getByTestId, queryByTestId, getByText, etc.)

    hashtag
    toBeDisabled

    This allows you to check whether an element is disabled from the user's perspective. According to the specification, the following elements can be : button, input, select, textarea, optgroup, option, fieldset, and custom elements.

    This custom matcher considers an element as disabled if the element is among the types of elements that can be disabled (listed above), and the disabled attribute is present. It will also consider the element as disabled if it's inside a parent form element that supports being disabled and has the disabled attribute present.

    Examples

    This custom matcher does not take into account the presence or absence of the aria-disabled attribute. For more on why this is the case, check .


    hashtag
    toBeEnabled

    This allows you to check whether an element is not disabled from the user's perspective.

    It works like not.toBeDisabled(). Use this matcher to avoid double negation in your tests.

    This custom matcher does not take into account the presence or absence of the aria-disabled attribute. For more on why this is the case, check .


    hashtag
    toBeEmptyDOMElement

    This allows you to assert whether an element has no visible content for the user. It ignores comments but will fail if the element contains white-space.

    Examples


    hashtag
    toBeInTheDocument

    This allows you to assert whether an element is present in the document or not.

    Examples

    Note: This matcher does not find detached elements. The element must be added to the document to be found by toBeInTheDocument. If you desire to search in a detached element please use: toContainElement


    hashtag
    toBeInvalid

    This allows you to check if an element, is currently invalid.

    An element is invalid if it has an with no value or a value of "true", or if the result of is false.

    Examples


    hashtag
    toBeRequired

    This allows you to check if a form element is currently required.

    An element is required if it is having a required or aria-required="true" attribute.

    Examples


    hashtag
    toBeValid

    This allows you to check if the value of an element, is currently valid.

    An element is valid if it has no s or an attribute value of "false". The result of must also be true if it's a form element.

    Examples


    hashtag
    toBeVisible

    This allows you to check if an element is currently visible to the user.

    An element is visible if all the following conditions are met:

    • it is present in the document

    • it does not have its css property display set to none

    • it does not have its css property visibility

    Examples


    hashtag
    toContainElement

    This allows you to assert whether an element contains another element as a descendant or not.

    Examples


    hashtag
    toContainHTML

    Assert whether a string representing a HTML element is contained in another element. The string should contain valid html, and not any incomplete html.

    Examples

    Chances are you probably do not need to use this matcher. We encourage testing from the perspective of how the user perceives the app in a browser. That's why testing against a specific DOM structure is not advised.

    It could be useful in situations where the code being tested renders html that was obtained from an external source, and you want to validate that that html code was used as intended.

    It should not be used to check DOM structure that you control. Please use toContainElement instead.


    hashtag
    toHaveAccessibleDescription

    This allows you to assert that an element has the expected .

    You can pass the exact string of the expected accessible description, or you can make a partial match passing a regular expression, or by using /.

    Examples


    hashtag
    toHaveAccessibleName

    This allows you to assert that an element has the expected . It is useful, for instance, to assert that form elements and buttons are properly labelled.

    You can pass the exact string of the expected accessible name, or you can make a partial match passing a regular expression, or by using /.

    Examples


    hashtag
    toHaveAttribute

    This allows you to check whether the given element has an attribute or not. You can also optionally check that the attribute has a specific expected value or partial match using /

    Examples


    hashtag
    toHaveClass

    This allows you to check whether the given element has certain classes within its class attribute.

    You must provide at least one class, unless you are asserting that an element does not have any classes.

    Examples


    hashtag
    toHaveFocus

    This allows you to assert whether an element has focus or not.

    Examples


    hashtag
    toHaveFormValues

    This allows you to check if a form or fieldset contains form controls for each given name, and having the specified value.

    It is important to stress that this matcher can only be invoked on a or a element.

    This allows it to take advantage of the property in form and fieldset to reliably fetch all form controls within them.

    This also avoids the possibility that users provide a container that contains more than one form, thereby intermixing form controls that are not related, and could even conflict with one another.

    This matcher abstracts away the particularities with which a form control value is obtained depending on the type of form control. For instance, <input> elements have a value attribute, but <select> elements do not. Here's a list of all cases covered:

    • <input type="number"> elements return the value as a number, instead of a string.

    • <input type="checkbox"> elements:

    The above rules make it easy, for instance, to switch from using a single select control to using a group of radio buttons. Or to switch from a multi select control, to using a group of checkboxes. The resulting set of form values used by this matcher to compare against would be the same.

    Examples

    hashtag
    toHaveStyle

    This allows you to check if a certain element has some specific css properties with specific values applied. It matches only if the element has all the expected properties applied, not just some of them.

    Examples

    This also works with rules that are applied to the element via a class name for which some rules are defined in a stylesheet currently active in the document. The usual rules of css precedence apply.


    hashtag
    toHaveTextContent

    This allows you to check whether the given node has a text content or not. This supports elements, but also text nodes and fragments.

    When a string argument is passed through, it will perform a partial case-sensitive match to the node content.

    To perform a case-insensitive match, you can use a RegExp with the /i modifier.

    If you want to match the whole content, you can use a RegExp to do it.

    Examples


    hashtag
    toHaveValue

    This allows you to check whether the given form element has the specified value. It accepts <input>, <select> and <textarea> elements with the exception of <input type="checkbox"> and <input type="radio">, which can be meaningfully matched only using toBeChecked or toHaveFormValues.

    For all other form elements, the value is matched using the same algorithm as in toHaveFormValues does.

    Examples

    Using DOM Testing Library


    hashtag
    toHaveDisplayValue

    This allows you to check whether the given form element has the specified displayed value (the one the end user will see). It accepts <input>, <select> and <textarea> elements with the exception of <input type="checkbox"> and <input type="radio">, which can be meaningfully matched only using toBeChecked or toHaveFormValues.

    Examples

    Using DOM Testing Library


    hashtag
    toBeChecked

    This allows you to check whether the given element is checked. It accepts an input of type checkbox or radio and elements with a role of checkbox, radio or switch with a valid aria-checked attribute of "true" or "false".

    Examples


    hashtag
    toBePartiallyChecked

    This allows you to check whether the given element is partially checked. It accepts an input of type checkbox and elements with a role of checkbox with a aria-checked="mixed", or input of type checkbox with indeterminate set to true

    Examples


    hashtag
    toHaveErrorMessage

    This allows you to check whether the given element has an or not.

    Use the aria-errormessage attribute to reference another element that contains custom error message text. Multiple ids is NOT allowed. Authors MUST use aria-invalid in conjunction with aria-errormessage. Learn more from .

    Whitespace is normalized.

    When a string argument is passed through, it will perform a whole case-sensitive match to the error message text.

    To perform a case-insensitive match, you can use a RegExp with the /i modifier.

    To perform a partial match, you can pass a RegExp or use expect.stringContaining("partial string").

    Examples

    hashtag
    Deprecated matchers

    hashtag
    toBeEmpty

    Note: This matcher is being deprecated due to a name clash with jest-extended. See more info in #216. In the future, please use only toBeEmptyDOMElement

    This allows you to assert whether an element has content or not.

    Examples


    hashtag
    toBeInTheDOM

    This custom matcher is deprecated. Prefer toBeInTheDocument instead.

    This allows you to check whether a value is a DOM element, or not.

    Contrary to what its name implies, this matcher only checks that you passed to it a valid DOM element. It does not have a clear definition of what "the DOM" is. Therefore, it does not check whether that element is contained anywhere.

    This is the main reason why this matcher is deprecated, and will be removed in the next major release. You can follow the discussion around this decision in more detail .

    As an alternative, you can use toBeInTheDocument or toContainElement. Or if you just want to check if a value is indeed an HTMLElement you can always use some of :

    Note: The differences between toBeInTheDOM and toBeInTheDocument are significant. Replacing all uses of toBeInTheDOM with toBeInTheDocument will likely cause unintended consequences in your tests. Please make sure when replacing toBeInTheDOM to read through the documentation of the proposed alternatives to see which use case works better for your needs.

    hashtag
    toHaveDescription

    This custom matcher is deprecated. Prefer toHaveAccessibleDescription instead, which is more comprehensive in implementing the official spec.

    This allows you to check whether the given element has a description or not.

    An element gets its description via the . Set this to the id of one or more other elements. These elements may be nested inside, be outside, or a sibling of the passed in element.

    Whitespace is normalized. Using multiple ids will .

    When a string argument is passed through, it will perform a whole case-sensitive match to the description text.

    To perform a case-insensitive match, you can use a RegExp with the /i modifier.

    To perform a partial match, you can pass a RegExp or use expect.stringContaining("partial string").

    Examples

    hashtag
    Inspiration

    This whole library was extracted out of Kent C. Dodds' [DOM Testing Library][dom-testing-library], which was in turn extracted out of [React Testing Library][react-testing-library].

    The intention is to make this available to be used independently of these other libraries, and also to make it more clear that these other libraries are independent from jest, and can be used with other tests runners as well.

    hashtag
    Other Solutions

    I'm not aware of any, if you are please [make a pull request][prs] and add it here!

    If you would like to further test the accessibility and validity of the DOM consider . It doesn't overlap with jest-dom but can complement it for more in-depth accessibility checking (eg: validating aria attributes or ensuring unique id attributes).

    hashtag
    Guiding Principles

    [The more your tests resemble the way your software is used, the more confidence they can give you.][guiding-principle]

    This library follows the same guiding principles as its mother library [DOM Testing Library][dom-testing-library]. Go [check them out][guiding-principle] for more details.

    Additionally, with respect to custom DOM matchers, this library aims to maintain a minimal but useful set of them, while avoiding bloating itself with merely convenient ones that can be easily achieved with other APIs. In general, the overall criteria for what is considered a useful custom matcher to add to this library, is that doing the equivalent assertion on our own makes the test code more verbose, less clear in its intent, and/or harder to read.

    Custom matchers
    • toBeDisabled

    • toBeEnabled

    • toBeEmptyDOMElement

    • toBeInTheDocument

    • toBeInvalid

    • toBeRequired

    • toBeValid

    • toBeVisible

    • toContainElement

    • toContainHTML

    • toHaveAccessibleDescription

    • toHaveAccessibleName

    • toHaveAttribute

    • toHaveClass

    • toHaveFocus

    • toHaveFormValues

    • toHaveStyle

    • toHaveTextContent

    • toHaveValue

    • toHaveDisplayValue

    • toBeChecked

    • toBePartiallyChecked

    • toHaveErrorMessage

  • Deprecated matchers

    • toBeEmpty

    • toBeInTheDOM

    • toHaveDescription

  • Inspiration

  • Other Solutions

  • Guiding Principles

  • Contributors

  • LICENSE

  • set to either
    hidden
    or
    collapse
  • it does not have its css property opacity set to 0

  • its parent element is also visible (and so on up to the top of the DOM tree)

  • it does not have the hiddenarrow-up-right attribute

  • if <details /> it has the open attribute

  • if there's a single one with the given name attribute, it is treated as a boolean, returning true if the checkbox is checked, false if unchecked.
  • if there's more than one checkbox with the same name attribute, they are all treated collectively as a single form control, which returns the value as an array containing all the values of the selected checkboxes in the collection.

  • <input type="radio"> elements are all grouped by the name attribute, and such a group treated as a single form control. This form control returns the value as a string corresponding to the value attribute of the selected radio button within the group.

  • <input type="text"> elements return the value as a string. This also applies to <input> elements having any other possible type attribute that's not explicitly covered in different rules above (e.g. search, email, date, password, hidden, etc.)

  • <select> elements without the multiple attribute return the value as a string corresponding to the value attribute of the selected option, or undefined if there's no selected option.

  • <select multiple> elements return the value as an array containing all the values of the selected optionsarrow-up-right.

  • <textarea> elements return their value as a string. The value corresponds to their node content.

  • yarnarrow-up-right
    eslint-plugin-jest-domarrow-up-right
    tests setup filearrow-up-right
    disabledarrow-up-right
    #144arrow-up-right
    #144arrow-up-right
    aria-invalid attributearrow-up-right
    checkValidity()arrow-up-right
    aria-invalid attributearrow-up-right
    checkValidity()arrow-up-right
    accessible descriptionarrow-up-right
    expect.stringContainingarrow-up-right
    expect.stringMatchingarrow-up-right
    accessible namearrow-up-right
    expect.stringContainingarrow-up-right
    expect.stringMatchingarrow-up-right
    expect.stringContainingarrow-up-right
    expect.stringMatchingarrow-up-right
    formarrow-up-right
    fieldsetarrow-up-right
    .elementsarrow-up-right
    ARIA error messagearrow-up-right
    aria-errormessage specarrow-up-right
    herearrow-up-right
    jest's built-in matchersarrow-up-right
    aria-describedby attributearrow-up-right
    join the referenced elements’ text content separated by a spacearrow-up-right
    jest-axearrow-up-right
    npm install --save-dev @testing-library/jest-dom
    yarn add --dev @testing-library/jest-dom
    // In your own jest-setup.js (or any other name)
    import "@testing-library/jest-dom";
    
    // In jest.config.js add (if you haven't already)
    setupFilesAfterEnv: ["<rootDir>/jest-setup.js"];
      // In tsconfig.json
      "include": [
        ...
        "./jest-setup.ts"
      ],
    toBeDisabled();
    <button data-testid="button" type="submit" disabled>submit</button>
    <fieldset disabled><input type="text" data-testid="input" /></fieldset>
    <a href="..." disabled>link</a>
    expect(getByTestId("button")).toBeDisabled();
    expect(getByTestId("input")).toBeDisabled();
    expect(getByText("link")).not.toBeDisabled();
    toBeEnabled();
    toBeEmptyDOMElement();
    <span data-testid="not-empty"><span data-testid="empty"></span></span>
    <span data-testid="with-whitespace"> </span>
    <span data-testid="with-comment"><!-- comment --></span>
    expect(getByTestId("empty")).toBeEmptyDOMElement();
    expect(getByTestId("not-empty")).not.toBeEmptyDOMElement();
    expect(getByTestId("with-whitespace")).not.toBeEmptyDOMElement();
    toBeInTheDocument();
    <span data-testid="html-element"><span>Html Element</span></span>
    <svg data-testid="svg-element"></svg>
    expect(
      getByTestId(document.documentElement, "html-element")
    ).toBeInTheDocument();
    expect(
      getByTestId(document.documentElement, "svg-element")
    ).toBeInTheDocument();
    expect(
      queryByTestId(document.documentElement, "does-not-exist")
    ).not.toBeInTheDocument();
    toBeInvalid();
    <input data-testid="no-aria-invalid" />
    <input data-testid="aria-invalid" aria-invalid />
    <input data-testid="aria-invalid-value" aria-invalid="true" />
    <input data-testid="aria-invalid-false" aria-invalid="false" />
    
    <form data-testid="valid-form">
      <input />
    </form>
    
    <form data-testid="invalid-form">
      <input required />
    </form>
    expect(getByTestId("no-aria-invalid")).not.toBeInvalid();
    expect(getByTestId("aria-invalid")).toBeInvalid();
    expect(getByTestId("aria-invalid-value")).toBeInvalid();
    expect(getByTestId("aria-invalid-false")).not.toBeInvalid();
    
    expect(getByTestId("valid-form")).not.toBeInvalid();
    expect(getByTestId("invalid-form")).toBeInvalid();
    toBeRequired();
    <input data-testid="required-input" required />
    <input data-testid="aria-required-input" aria-required="true" />
    <input data-testid="conflicted-input" required aria-required="false" />
    <input data-testid="aria-not-required-input" aria-required="false" />
    <input data-testid="optional-input" />
    <input data-testid="unsupported-type" type="image" required />
    <select data-testid="select" required></select>
    <textarea data-testid="textarea" required></textarea>
    <div data-testid="supported-role" role="tree" required></div>
    <div data-testid="supported-role-aria" role="tree" aria-required="true"></div>
    expect(getByTestId("required-input")).toBeRequired();
    expect(getByTestId("aria-required-input")).toBeRequired();
    expect(getByTestId("conflicted-input")).toBeRequired();
    expect(getByTestId("aria-not-required-input")).not.toBeRequired();
    expect(getByTestId("optional-input")).not.toBeRequired();
    expect(getByTestId("unsupported-type")).not.toBeRequired();
    expect(getByTestId("select")).toBeRequired();
    expect(getByTestId("textarea")).toBeRequired();
    expect(getByTestId("supported-role")).not.toBeRequired();
    expect(getByTestId("supported-role-aria")).toBeRequired();
    toBeValid();
    <input data-testid="no-aria-invalid" />
    <input data-testid="aria-invalid" aria-invalid />
    <input data-testid="aria-invalid-value" aria-invalid="true" />
    <input data-testid="aria-invalid-false" aria-invalid="false" />
    
    <form data-testid="valid-form">
      <input />
    </form>
    
    <form data-testid="invalid-form">
      <input required />
    </form>
    expect(getByTestId("no-aria-invalid")).toBeValid();
    expect(getByTestId("aria-invalid")).not.toBeValid();
    expect(getByTestId("aria-invalid-value")).not.toBeValid();
    expect(getByTestId("aria-invalid-false")).toBeValid();
    
    expect(getByTestId("valid-form")).toBeValid();
    expect(getByTestId("invalid-form")).not.toBeValid();
    toBeVisible();
    <div data-testid="zero-opacity" style="opacity: 0">Zero Opacity Example</div>
    <div data-testid="visibility-hidden" style="visibility: hidden">
      Visibility Hidden Example
    </div>
    <div data-testid="display-none" style="display: none">Display None Example</div>
    <div style="opacity: 0">
      <span data-testid="hidden-parent">Hidden Parent Example</span>
    </div>
    <div data-testid="visible">Visible Example</div>
    <div data-testid="hidden-attribute" hidden>Hidden Attribute Example</div>
    expect(getByText("Zero Opacity Example")).not.toBeVisible();
    expect(getByText("Visibility Hidden Example")).not.toBeVisible();
    expect(getByText("Display None Example")).not.toBeVisible();
    expect(getByText("Hidden Parent Example")).not.toBeVisible();
    expect(getByText("Visible Example")).toBeVisible();
    expect(getByText("Hidden Attribute Example")).not.toBeVisible();
    toContainElement(element: HTMLElement | SVGElement | null)
    <span data-testid="ancestor"><span data-testid="descendant"></span></span>
    const ancestor = getByTestId("ancestor");
    const descendant = getByTestId("descendant");
    const nonExistantElement = getByTestId("does-not-exist");
    
    expect(ancestor).toContainElement(descendant);
    expect(descendant).not.toContainElement(ancestor);
    expect(ancestor).not.toContainElement(nonExistantElement);
    toContainHTML(htmlText: string)
    <span data-testid="parent"><span data-testid="child"></span></span>
    // These are valid uses
    expect(getByTestId("parent")).toContainHTML(
      '<span data-testid="child"></span>'
    );
    expect(getByTestId("parent")).toContainHTML('<span data-testid="child" />');
    expect(getByTestId("parent")).not.toContainHTML("<br />");
    
    // These won't work
    expect(getByTestId("parent")).toContainHTML('data-testid="child"');
    expect(getByTestId("parent")).toContainHTML("data-testid");
    expect(getByTestId("parent")).toContainHTML("</span>");
    toHaveAccessibleDescription(expectedAccessibleDescription?: string | RegExp)
    <a
      data-testid="link"
      href="/"
      aria-label="Home page"
      title="A link to start over"
      >Start</a
    >
    <a data-testid="extra-link" href="/about" aria-label="About page">About</a>
    <img src="avatar.jpg" data-testid="avatar" alt="User profile pic" />
    <img
      src="logo.jpg"
      data-testid="logo"
      alt="Company logo"
      aria-describedby="t1"
    />
    <span id="t1" role="presentation">The logo of Our Company</span>
    expect(getByTestId("link")).toHaveAccessibleDescription();
    expect(getByTestId("link")).toHaveAccessibleDescription("A link to start over");
    expect(getByTestId("link")).not.toHaveAccessibleDescription("Home page");
    expect(getByTestId("extra-link")).not.toHaveAccessibleDescription();
    expect(getByTestId("avatar")).not.toHaveAccessibleDescription();
    expect(getByTestId("logo")).not.toHaveAccessibleDescription("Company logo");
    expect(getByTestId("logo")).toHaveAccessibleDescription(
      "The logo of Our Company"
    );
    toHaveAccessibleName(expectedAccessibleName?: string | RegExp)
    <img data-testid="img-alt" src="" alt="Test alt" />
    <img data-testid="img-empty-alt" src="" alt="" />
    <svg data-testid="svg-title"><title>Test title</title></svg>
    <button data-testid="button-img-alt"><img src="" alt="Test" /></button>
    <p><img data-testid="img-paragraph" src="" alt="" /> Test content</p>
    <button data-testid="svg-button"><svg><title>Test</title></svg></p>
    <div><svg data-testid="svg-without-title"></svg></div>
    <input data-testid="input-title" title="test" />
    expect(getByTestId("img-alt")).toHaveAccessibleName("Test alt");
    expect(getByTestId("img-empty-alt")).not.toHaveAccessibleName();
    expect(getByTestId("svg-title")).toHaveAccessibleName("Test title");
    expect(getByTestId("button-img-alt")).toHaveAccessibleName();
    expect(getByTestId("img-paragraph")).not.toHaveAccessibleName();
    expect(getByTestId("svg-button")).toHaveAccessibleName();
    expect(getByTestId("svg-without-title")).not.toHaveAccessibleName();
    expect(getByTestId("input-title")).toHaveAccessibleName();
    toHaveAttribute(attr: string, value?: any)
    <button data-testid="ok-button" type="submit" disabled>ok</button>
    const button = getByTestId("ok-button");
    
    expect(button).toHaveAttribute("disabled");
    expect(button).toHaveAttribute("type", "submit");
    expect(button).not.toHaveAttribute("type", "button");
    
    expect(button).toHaveAttribute("type", expect.stringContaining("sub"));
    expect(button).toHaveAttribute("type", expect.not.stringContaining("but"));
    toHaveClass(...classNames: string[], options?: {exact: boolean})
    <button data-testid="delete-button" class="btn extra btn-danger">
      Delete item
    </button>
    <button data-testid="no-classes">No Classes</button>
    const deleteButton = getByTestId("delete-button");
    const noClasses = getByTestId("no-classes");
    
    expect(deleteButton).toHaveClass("extra");
    expect(deleteButton).toHaveClass("btn-danger btn");
    expect(deleteButton).toHaveClass("btn-danger", "btn");
    expect(deleteButton).not.toHaveClass("btn-link");
    
    expect(deleteButton).toHaveClass("btn-danger extra btn", { exact: true }); // to check if the element has EXACTLY a set of classes
    expect(deleteButton).not.toHaveClass("btn-danger extra", { exact: true }); // if it has more than expected it is going to fail
    
    expect(noClasses).not.toHaveClass();
    toHaveFocus();
    <div><input type="text" data-testid="element-to-focus" /></div>
    const input = getByTestId("element-to-focus");
    
    input.focus();
    expect(input).toHaveFocus();
    
    input.blur();
    expect(input).not.toHaveFocus();
    toHaveFormValues(expectedValues: {
      [name: string]: any
    })
    <form data-testid="login-form">
      <input type="text" name="username" value="jane.doe" />
      <input type="password" name="password" value="12345678" />
      <input type="checkbox" name="rememberMe" checked />
      <button type="submit">Sign in</button>
    </form>
    expect(getByTestId("login-form")).toHaveFormValues({
      username: "jane.doe",
      rememberMe: true,
    });
    toHaveStyle(css: string | object)
    <button
      data-testid="delete-button"
      style="display: none; background-color: red"
    >
      Delete item
    </button>
    const button = getByTestId("delete-button");
    
    expect(button).toHaveStyle("display: none");
    expect(button).toHaveStyle({ display: "none" });
    expect(button).toHaveStyle(`
      background-color: red;
      display: none;
    `);
    expect(button).toHaveStyle({
      backgroundColor: "red",
      display: "none",
    });
    expect(button).not.toHaveStyle(`
      background-color: blue;
      display: none;
    `);
    expect(button).not.toHaveStyle({
      backgroundColor: "blue",
      display: "none",
    });
    toHaveTextContent(text: string | RegExp, options?: {normalizeWhitespace: boolean})
    <span data-testid="text-content">Text Content</span>
    const element = getByTestId("text-content");
    
    expect(element).toHaveTextContent("Content");
    expect(element).toHaveTextContent(/^Text Content$/); // to match the whole content
    expect(element).toHaveTextContent(/content$/i); // to use case-insensitive match
    expect(element).not.toHaveTextContent("content");
    toHaveValue(value: string | string[] | number)
    <input type="text" value="text" data-testid="input-text" />
    <input type="number" value="5" data-testid="input-number" />
    <input type="text" data-testid="input-empty" />
    <select multiple data-testid="select-number">
      <option value="first">First Value</option>
      <option value="second" selected>Second Value</option>
      <option value="third" selected>Third Value</option>
    </select>
    const textInput = getByTestId("input-text");
    const numberInput = getByTestId("input-number");
    const emptyInput = getByTestId("input-empty");
    const selectInput = getByTestId("select-number");
    
    expect(textInput).toHaveValue("text");
    expect(numberInput).toHaveValue(5);
    expect(emptyInput).not.toHaveValue();
    expect(selectInput).toHaveValue(["second", "third"]);
    toHaveDisplayValue(value: string | RegExp | (string|RegExp)[])
    <label for="input-example">First name</label>
    <input type="text" id="input-example" value="Luca" />
    
    <label for="textarea-example">Description</label>
    <textarea id="textarea-example">An example description here.</textarea>
    
    <label for="single-select-example">Fruit</label>
    <select id="single-select-example">
      <option value="">Select a fruit...</option>
      <option value="banana">Banana</option>
      <option value="ananas">Ananas</option>
      <option value="avocado">Avocado</option>
    </select>
    
    <label for="multiple-select-example">Fruits</label>
    <select id="multiple-select-example" multiple>
      <option value="">Select a fruit...</option>
      <option value="banana" selected>Banana</option>
      <option value="ananas">Ananas</option>
      <option value="avocado" selected>Avocado</option>
    </select>
    const input = screen.getByLabelText("First name");
    const textarea = screen.getByLabelText("Description");
    const selectSingle = screen.getByLabelText("Fruit");
    const selectMultiple = screen.getByLabelText("Fruits");
    
    expect(input).toHaveDisplayValue("Luca");
    expect(input).toHaveDisplayValue(/Luc/);
    expect(textarea).toHaveDisplayValue("An example description here.");
    expect(textarea).toHaveDisplayValue(/example/);
    expect(selectSingle).toHaveDisplayValue("Select a fruit...");
    expect(selectSingle).toHaveDisplayValue(/Select/);
    expect(selectMultiple).toHaveDisplayValue([/Avocado/, "Banana"]);
    toBeChecked();
    <input type="checkbox" checked data-testid="input-checkbox-checked" />
    <input type="checkbox" data-testid="input-checkbox-unchecked" />
    <div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
    <div
      role="checkbox"
      aria-checked="false"
      data-testid="aria-checkbox-unchecked"
    />
    
    <input type="radio" checked value="foo" data-testid="input-radio-checked" />
    <input type="radio" value="foo" data-testid="input-radio-unchecked" />
    <div role="radio" aria-checked="true" data-testid="aria-radio-checked" />
    <div role="radio" aria-checked="false" data-testid="aria-radio-unchecked" />
    <div role="switch" aria-checked="true" data-testid="aria-switch-checked" />
    <div role="switch" aria-checked="false" data-testid="aria-switch-unchecked" />
    const inputCheckboxChecked = getByTestId("input-checkbox-checked");
    const inputCheckboxUnchecked = getByTestId("input-checkbox-unchecked");
    const ariaCheckboxChecked = getByTestId("aria-checkbox-checked");
    const ariaCheckboxUnchecked = getByTestId("aria-checkbox-unchecked");
    expect(inputCheckboxChecked).toBeChecked();
    expect(inputCheckboxUnchecked).not.toBeChecked();
    expect(ariaCheckboxChecked).toBeChecked();
    expect(ariaCheckboxUnchecked).not.toBeChecked();
    
    const inputRadioChecked = getByTestId("input-radio-checked");
    const inputRadioUnchecked = getByTestId("input-radio-unchecked");
    const ariaRadioChecked = getByTestId("aria-radio-checked");
    const ariaRadioUnchecked = getByTestId("aria-radio-unchecked");
    expect(inputRadioChecked).toBeChecked();
    expect(inputRadioUnchecked).not.toBeChecked();
    expect(ariaRadioChecked).toBeChecked();
    expect(ariaRadioUnchecked).not.toBeChecked();
    
    const ariaSwitchChecked = getByTestId("aria-switch-checked");
    const ariaSwitchUnchecked = getByTestId("aria-switch-unchecked");
    expect(ariaSwitchChecked).toBeChecked();
    expect(ariaSwitchUnchecked).not.toBeChecked();
    toBePartiallyChecked();
    <input type="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
    <input type="checkbox" checked data-testid="input-checkbox-checked" />
    <input type="checkbox" data-testid="input-checkbox-unchecked" />
    <div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
    <div
      role="checkbox"
      aria-checked="false"
      data-testid="aria-checkbox-unchecked"
    />
    <input type="checkbox" data-testid="input-checkbox-indeterminate" />
    const ariaCheckboxMixed = getByTestId("aria-checkbox-mixed");
    const inputCheckboxChecked = getByTestId("input-checkbox-checked");
    const inputCheckboxUnchecked = getByTestId("input-checkbox-unchecked");
    const ariaCheckboxChecked = getByTestId("aria-checkbox-checked");
    const ariaCheckboxUnchecked = getByTestId("aria-checkbox-unchecked");
    const inputCheckboxIndeterminate = getByTestId("input-checkbox-indeterminate");
    
    expect(ariaCheckboxMixed).toBePartiallyChecked();
    expect(inputCheckboxChecked).not.toBePartiallyChecked();
    expect(inputCheckboxUnchecked).not.toBePartiallyChecked();
    expect(ariaCheckboxChecked).not.toBePartiallyChecked();
    expect(ariaCheckboxUnchecked).not.toBePartiallyChecked();
    
    inputCheckboxIndeterminate.indeterminate = true;
    expect(inputCheckboxIndeterminate).toBePartiallyChecked();
    toHaveErrorMessage(text: string | RegExp)
    <label for="startTime"> Please enter a start time for the meeting: </label>
    <input
      id="startTime"
      type="text"
      aria-errormessage="msgID"
      aria-invalid="true"
      value="11:30 PM"
    />
    <span id="msgID" aria-live="assertive" style="visibility:visible">
      Invalid time: the time must be between 9:00 AM and 5:00 PM
    </span>
    const timeInput = getByLabel("startTime");
    
    expect(timeInput).toHaveErrorMessage(
      "Invalid time: the time must be between 9:00 AM and 5:00 PM"
    );
    expect(timeInput).toHaveErrorMessage(/invalid time/i); // to partially match
    expect(timeInput).toHaveErrorMessage(expect.stringContaining("Invalid time")); // to partially match
    expect(timeInput).not.toHaveErrorMessage("Pikachu!");
    toBeEmpty();
    <span data-testid="not-empty"><span data-testid="empty"></span></span>
    expect(getByTestId("empty")).toBeEmpty();
    expect(getByTestId("not-empty")).not.toBeEmpty();
    toBeInTheDOM();
    expect(document.querySelector(".ok-button")).toBeInstanceOf(HTMLElement);
    expect(document.querySelector(".cancel-button")).toBeTruthy();
    toHaveDescription(text: string | RegExp)
    <button aria-label="Close" aria-describedby="description-close">X</button>
    <div id="description-close">Closing will discard any changes</div>
    
    <button>Delete</button>
    const closeButton = getByRole("button", { name: "Close" });
    
    expect(closeButton).toHaveDescription("Closing will discard any changes");
    expect(closeButton).toHaveDescription(/will discard/); // to partially match
    expect(closeButton).toHaveDescription(expect.stringContaining("will discard")); // to partially match
    expect(closeButton).toHaveDescription(/^closing/i); // to use case-insensitive match
    expect(closeButton).not.toHaveDescription("Other description");
    
    const deleteButton = getByRole("button", { name: "Delete" });
    expect(deleteButton).not.toHaveDescription();
    expect(deleteButton).toHaveDescription(""); // Missing or empty description always becomes a blank string