Keywords: React Testing Library | jest-dom | Unit Testing | Jest | DOM Assertions
Abstract: This article provides an in-depth analysis of the 'expect(...).toBeInTheDocument is not a function' error in React Testing Library tests, explaining that this assertion method is not built into RTL but comes from the jest-dom extension library. It offers a complete installation and configuration guide, including installing @testing-library/jest-dom via npm, importing the extension in test files, and setting up setupFilesAfterEnv in Jest configuration. By comparing erroneous code with corrected implementations, it helps developers understand how to properly use DOM state assertions to verify element visibility.
Problem Background and Error Analysis
When using React Testing Library (RTL) for component testing, developers often encounter the error TypeError: expect(...).toBeInTheDocument is not a function. This error typically occurs when trying to use the toBeInTheDocument() assertion method to verify if an element exists in the document. For example, when testing the show and hide functionality of a tooltip component, the code might look like this:
it('should show and hide the message using onMouseOver and onMouseOut events respectively', () => {
const { queryByTestId, queryByText } = render(
<Tooltip id="test" message="test" />
)
fireEvent.mouseOver(queryByTestId('tooltip'))
expect(queryByText('test')).toBeInTheDocument()
fireEvent.mouseOut(queryByTestId('tooltip'))
expect(queryByText('test')).not.toBeInTheDocument()
cleanup()
})Although other testing methods like queryByText and queryByTestId work correctly, calling toBeInTheDocument throws an error. This happens because toBeInTheDocument is not part of RTL's core but is a custom Jest matcher provided by the @testing-library/jest-dom extension library.
Root Cause
toBeInTheDocument is an assertion method defined by the jest-dom library, used to check if an element is actually present in the DOM. RTL itself only provides querying and interaction functions, such as render, fireEvent, etc., and does not include advanced assertions. If jest-dom is not properly installed and imported, the Jest environment will not recognize toBeInTheDocument, causing it to be treated as an undefined function.
Solution and Implementation Steps
To resolve this issue, you need to install and configure the @testing-library/jest-dom extension. Here are the detailed steps:
- Install the jest-dom package: Add the dependency via npm or yarn. For example, using npm, run the following command:
This adds the library to dev dependencies, ensuring the testing environment can access its functionality.npm install --save-dev @testing-library/jest-dom - Import the extension: Import
jest-domin your test files to extend Jest's expect object. Typically, add this at the top of the file:
For TypeScript projects, you can also useimport '@testing-library/jest-dom'import '@testing-library/jest-dom/extend-expect'for type support. - Configure Jest: To automatically import the extension in all test files, set up
setupFilesAfterEnvin the Jest configuration file (e.g.,jest.config.js). For example:
Here, themodule.exports = { setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'] }setupTests.tsfile should include the import statementimport '@testing-library/jest-dom'. This ensures the extension is loaded after the test environment initializes, avoiding repetitive imports in each file.
A corrected test code example is as follows:
import '@testing-library/jest-dom'
import { render, fireEvent, cleanup } from '@testing-library/react'
import Tooltip from './Tooltip'
it('should show and hide the message using onMouseOver and onMouseOut events respectively', () => {
const { queryByTestId, queryByText } = render(
<Tooltip id="test" message="test" />
)
fireEvent.mouseOver(queryByTestId('tooltip'))
expect(queryByText('test')).toBeInTheDocument()
fireEvent.mouseOut(queryByTestId('tooltip'))
expect(queryByText('test')).not.toBeInTheDocument()
cleanup()
})This code now runs correctly because jest-dom extends the expect API, adding the toBeInTheDocument method.
In-Depth Analysis and Best Practices
The toBeInTheDocument assertion checks if an element is under the document root node based on its ownerDocument property, which is particularly useful for verifying dynamically displayed components like tooltips. Unlike simply checking for element existence, it ensures the element is part of the DOM, not just in memory.
In testing, using queryByText with toBeInTheDocument efficiently validates UI states after interactions. For instance, after a mouseover event, the tooltip element is set to display: block, and expect(queryByText('test')).toBeInTheDocument() passes; after mouseout, the element is hidden (display: none), and expect(queryByText('test')).not.toBeInTheDocument() ensures it is no longer visible.
Common mistakes include forgetting to import the extension or misconfiguring Jest. If the issue persists, check Node.js version compatibility and package manager cache (run npm cache clean --force and reinstall). Additionally, ensure test file extensions (e.g., .ts for TypeScript) match the configuration.
As a supplement, other answers mention importing the extension in setupTests.ts, which is suitable for large projects to maintain code cleanliness. However, importing directly in each test file is also feasible, especially for small applications. The choice depends on project structure and maintenance needs.
In summary, by properly integrating jest-dom, developers can fully leverage RTL's assertion capabilities, writing more reliable and readable test cases to enhance the quality and efficiency of front-end testing.