Blog

Testing Library - Queries

11.02.2022 | Testing | Shavina Chau

hero

The Testing Library family (including React Testing Library) is a great tool to help you write comprehensive, maintainable UI tests.

The two primary functions it offers are queries, and user actions. Queries are the methods that Testing Library gives you to find elements on the page [6].

This article explains the three types of queries ("getBy", "findBy", "queryBy") and how to use each one in different scenarios with code samples. [Note A]

image

queryBy

queryBy should only be used if you want to assert that an element is not present [1].

Example use cases:

Asserting an element should not be present

expect(
  screen.queryByText("Error: Page Not Found")
).not.toBeInTheDocument();

Asserting an element only appears under certain conditions

Example: a form's Submit button does not appear until all input fields are filled.

const submitButton = screen.queryByText('submit');
expect(submitButton).not.toBeInTheDocument();

fireEvent.change(name, { target: { value: "Jane Smith" } });
expect(submitButton).toBeInTheDocument();

Asserting an element is in the correct initial state

Example: a button that is initially "On", not "Off".

// notice getBy can be used here as we expect this to appear
expect(
  screen.getByRole("button", {
    name: /On/i,
  })
).toBeInTheDocument();

// while queryBy is used here as we expect this *not* to appear
expect(
  screen.queryByRole("button", {
    name: /Off/i,
  })
).not.toBeInTheDocument();

Asserting a list of elements is empty

const projectCards = screen.queryAllByTestId("project-card");
expect(projectCards).toHaveLength(0);

Asserting an element disappears or is removed

This example is taken from the Testing Library documentation on disappearance [2].

The waitFor async helper function retries until the wrapped function stops throwing an error. This can be used to assert that an element disappears from the page.

test('movie title goes away', async () => {
  // element is initially present...
  // note use of queryBy instead of getBy to return null
  // instead of throwing in the query itself
  await waitFor(() => {
    expect(queryByText('i, robot')).not.toBeInTheDocument()
  })
})

There is also a waitForElementToBeRemoved helper, but in my experience I find await waitFor + queryBy to be more reliable, especially if expecting an element to disappear after a fireEvent or userEvent action. [Note B]

getBy

getBy is used to get an element on the page, and will throw an error if no matching element is immediately found.

Two common use cases are:

  • Asserting an element is present on the page, or asserting particular properties about that element.

  • Selecting an element on the page to simulate user interaction with it, such as clicking it, entering text, or selecting an input.

While queryBy can also be used for those purposes, the main reason to prefer getBy over queryBy is that getBy will generally give you more helpful error messages when the element is not found.

Consider the following examples:

Example 1 - asserting an element is present

// getBy
expect(screen.getByText("Username")).toBeInTheDocument();

If this assertion fails, the error message is Unable to find an element with the text: Username. The error message clearly describes the desired element. It will also automatically print out the DOM so you can inspect the page yourself.

// queryBy
expect(screen.queryByText("Username")).toBeInTheDocument();

If this assertion fails, the error message may look something like "null is not in the document", or, even more confusingly, "received value must be an HTMLElement or an SVGElement. Received has value: null". There's not much information about what went wrong in these error messages, since the assertion can only see null as the actual value.

Example 2 - interacting with an element

// getBy
const next = screen.getByRole("button", { name: /Next/i });
fireEvent.click(next);

If the button is not on the page, getBy will throw an error with message "Unable to find an accessible element with the role "button" and name /Next/i", which clearly describes the expected element. As mentioned in Example 1, it will also automatically print out the DOM to help in debugging.

// queryBy
const next = screen.queryByRole("button", { name: /Next/i });
fireEvent.click(next);

If the button is not on the page, queryBy will not throw - instead, it returns null. The test will instead fail on the next line, when the fireEvent call throws with the error: Error: Unable to fire a "click" event - please provide a DOM element. This error message is less obvious since queryBy quietly returned null, and does not provide any information about the missing element - this requires more time and effort to debug.

findBy

findBy is used to get an element that may not appear on the page immediately. For example, expecting a modal to appear after a button click, or expecting new content to appear after an asynchronous API call returns.

findBy will return a Promise, so you must use await or .then() [3]. It will throw an error if the element does not appear after a specified timeout. [Note C]

Use cases for findBy are the same as getBy, but in situations where you may need to wait for the desired element to be on the page.

Notes

A. The code examples shown here use Jest and jest-dom, which provide custom matchers like toBeInTheDocument() [4].

B. Because fireEvent and userEvent are automatically wrapped in act, all React state updates are completed before the next line is executed. This means if an element is rendered (or not) based on simple state changes, attempting to use waitForElementToBeRemoved results in "Error: The element(s) given to waitForElementToBeRemoved are already removed. waitForElementToBeRemoved requires that the element(s) exist(s) before waiting for removal." since the element was already removed during the state update.

According to [5], waitForElementToBeRemoved is meant to await for async operations and in the example attached, you don't have anything async but just a simple react state change. In that scenario, there's no need to wait for anything."

C. Behind the scenes, findBy is a combination of getBy and waitFor, and accepts waitFor options as the second parameter, allowing you to customize values like the timeout.

Resources

  1. https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#using-query-variants-for-anything-except-checking-for-non-existence

  2. https://testing-library.com/docs/guide-disappearance/#waiting-for-disappearance

  3. https://testing-library.com/docs/dom-testing-library/api-async/#findby-queries

  4. https://testing-library.com/docs/ecosystem-jest-dom/

  5. https://github.com/testing-library/react-testing-library/issues/1033#issuecomment-1107477476

  6. https://testing-library.com/docs/queries/about

  7. https://testing-library.com/docs/dom-testing-library/cheatsheet/

  8. Image source: "Selection of the Folio Society books in the library of Warwick Carter" by Warwick Carter is licensed under CC BY-NC 2.0

Topics: Testing Library, React Testing Library, getBy vs findBy vs queryBy, JavaScript, Jest

Share

Read More

Related Posts

related_image

12.02.2022 | Certifications | Sarah Kainec

When I first set out to become a Stripe Certified Professional Implementation Architect, I had a lot of questions...

related_image

11.02.2022 | Testing | Dave Schinkel

When you read about or hear about Test Driven Development, the first thing folks are always told is that in a TDD cycle...

related_image

10.13.2022 | Culture | Ryan Taylor

Today I am announcing my transition to the Delivery Lead team at Focused Labs

related_image

10.12.2022 | Psychological-safety Teams Culture | Katy Scott

Over the last year our teams have been learning about psychological safety

related_image

01.25.2022 | Tutorial Culture | Peter Swartwout

If you've ever taken a fitness class, you've heard the instructor talk about consistency. It's not about how fast you pedal or how much weight you lift...

related_image

01.13.2022 | Culture | Kate Courtney

Pair programming is a way to develop software. It used to involve two software developers sitting down at one workstation and writing code together.

No src

accent
FocusedLabs

433 W Van Buren St
Suite 1100-B
Chicago, IL 60607
(708) 303-8088

work@focusedlabs.io

© 2022 Focused Labs, All Rights Reserved.

  • Facebook icon
  • Twitter icon
  • LinkedIn icon
  • Github icon