@testing-library/jest-dom is a set of custom DOM matchers for Jest, and because it only runs during tests it poses very little direct risk to your production security — the real concern is keeping it in devDependencies, patched, and out of your shipped bundle. If you write frontend tests, you almost certainly import jest-dom to get readable assertions like toBeInTheDocument() and toHaveAttribute(). This guide explains what testing-library/jest-dom actually does and how to treat it correctly from a supply chain point of view.
What jest-dom adds
Jest ships with generic matchers like toBe and toEqual. Those are awkward for DOM assertions because you end up reaching into element properties by hand. The jest dom package extends expect with matchers that read like the thing you are checking:
import '@testing-library/jest-dom'
test('submit button is disabled until form is valid', () => {
render(<SignupForm />)
const button = screen.getByRole('button', { name: /sign up/i })
expect(button).toBeDisabled()
expect(button).toHaveAttribute('type', 'submit')
expect(screen.getByLabelText(/email/i)).toBeRequired()
})
Matchers such as toBeVisible, toHaveTextContent, toBeChecked, and toHaveClass make tests both shorter and clearer. That readability has a security dividend: assertions that plainly state intent are easier to review, so an access-control test that checks an admin control is hidden for a normal user is obvious rather than buried in property lookups.
It belongs in devDependencies only
The single most important thing to get right with testing library jest dom is where it lives in package.json. It is a test-time dependency and must be in devDependencies:
{
"devDependencies": {
"@testing-library/jest-dom": "^6.4.0",
"@testing-library/react": "^15.0.0",
"@testing-library/dom": "^10.0.0"
}
}
Two reasons. First, production installs (npm ci --omit=dev) then skip it entirely, so it never reaches a running server or a browser bundle, shrinking both your attack surface and your artifact size. Second, if a testing package ever picks up an advisory, a dev-only classification changes the urgency: a vulnerability that can only execute in your CI test runner is a different risk than one that ships to users. It still matters, but it is not a production incident.
If a bundler ever pulls jest-dom into client output, that is a misconfiguration worth fixing immediately — a test matcher has no business in the code you serve.
The dependency graph behind it
Jest-dom does not stand alone. It typically sits alongside @testing-library/react (or the framework equivalent) and depends on @testing-library/dom, the shared query engine. When you audit your project, look at the whole cluster rather than the top-level name. Most advisories in this space historically appear in transitive utility packages rather than in the matcher library itself.
A quick npm ls @testing-library/dom tells you how many copies and versions are resolved. Duplicate versions across the testing-library/dom tree are common in large monorepos and are worth deduplicating so you patch one place, not five. Automated software composition analysis will map these transitive relationships for you and flag any that carry a known issue, which is more reliable than eyeballing a lockfile.
Keeping it current without breaking tests
The jest-dom API is stable, but major versions occasionally change peer requirements — for example, aligning with a new Jest major or dropping old Node versions. Read the release notes before a major bump and run the suite in CI on the upgrade branch. Because these are dev tools, an upgrade that breaks is caught immediately by your own tests, which is the safest kind of failure.
Set up your dependency updates so testing packages are grouped and reviewed together. Bumping @testing-library/react, @testing-library/dom, and jest-dom in one coordinated change avoids the mismatched-version failures that come from upgrading them piecemeal.
Using it to test security behavior
Beyond hygiene, jest-dom is genuinely useful for asserting security-relevant UI behavior. You can verify that sensitive controls are not merely hidden with CSS but truly absent, that error messages do not echo raw user input, and that form fields carry the right autocomplete and type attributes:
test('password field does not expose value to autofill logging', () => {
render(<LoginForm />)
const pwd = screen.getByLabelText(/password/i)
expect(pwd).toHaveAttribute('type', 'password')
expect(pwd).toHaveAttribute('autocomplete', 'current-password')
})
These are cheap tests that catch regressions where a refactor accidentally turns a password input into a plain text field. Client-side checks are never your security boundary — the server always is — but they document intent and stop obvious UI regressions.
FAQ
Is @testing-library/jest-dom safe to use?
Yes. It is a dev-only matcher library that runs during tests, not in production. Keep it in devDependencies, patch it on a normal cadence, and make sure your bundler never ships it to the client, and its direct security impact is minimal.
Should jest-dom be in dependencies or devDependencies?
Always devDependencies. Production installs with --omit=dev will then exclude it, keeping it out of your runtime and your shipped bundle. Finding jest-dom in your client bundle means a build misconfiguration to fix.
What is the difference between jest-dom and @testing-library/dom?
@testing-library/dom is the query engine that finds elements (getByRole, getByText). @testing-library/jest-dom adds the assertion matchers (toBeInTheDocument, toBeDisabled). You usually have both, and jest-dom depends on the dom package underneath.
Can jest-dom introduce a supply chain vulnerability?
Only in your test/CI environment, since it does not ship to production. Most advisories in this ecosystem appear in transitive utility packages, so scan the whole testing-library cluster and deduplicate versions rather than trusting the top-level name alone.