Should I test that?

Should I test text content in the UI?

Verdict

No

No, do not write assertions whose only job is to check static UI copy such as headings and help text; find elements by role and visible name, and test only the text that the code computes from data.

Why

Do not test static copy. For a heading or a help text written literally in a React or Vue component, Blast radius is users, because customers read it, and Change frequency is regularly, because copy edits arrive about once a month. Detectability is immediately: the new wording sits in the diff and on the screen the author opens, and a test would repeat the string the author just typed. Reversibility is trivial, because a revert restores the old text, and Test cost is moderate, because every copy edit also means a test edit. Rule R8 gives Do not test; text computed from data moves Detectability to eventually and gets a test.

When the decision changes
WhenDecisionWhy
The code picks the text from data, such as a count with plural forms or an empty-state messageTest minimally: one test that renders one value per text variant and asserts the text shownDetectability moves to eventually, because the author looks at one state and '1 events' shows for one count only
The text is the accessible name of an icon button, set in aria-label and never shown on screenTest minimally: find the button by role and name in its interaction testDetectability moves to eventually, because sighted developers never read the label
The app ships in several languages, with one translation file per localeTest: a CI check that every key of the source locale exists in every other locale fileDetectability moves to eventually, because nobody on the team reads every locale, and Test cost falls to trivial
The law prescribes the wording, such as the consent text beside a marketing email checkboxTest mandatory: assert the exact required wording on every form that collects the consentBlast radius rises to safety-or-legal, because consent collected under other wording can be invalid
The copy changes in most weeks, such as the headline of a marketing pageDo not test the wording; review the copy in the pull request and on the preview deploymentChange frequency rises to constantly, but Detectability stays immediately and Reversibility trivial, so the decision holds

What breaks if you don't test

A test that asserts 'Save changes' fails only when someone changes that string on purpose, so dropping it loses nothing. The text failures that reach customers come from text the code builds: "You have 1 events", an empty state that never appears because a condition flipped, or a raw key such as settings.title on the German page. Computed text breaks in states the author did not open, and customers find the error days later.

What you lose if you over-test

When every label has an assertion, a content designer who rewrites 30 labels fails dozens of tests, and a developer spends hours pasting new strings into them. Copy assertions never fail for a bug, so the team learns to update them without reading, and a real text regression goes through the same way. Tests that find elements by a whole sentence also break when a comma moves.

What to do instead

Review wording in the pull request diff and on the preview deployment. In component tests, find elements as the Testing Library guiding principles describe, by role and accessible name, as in getByRole('button', { name: /save/i }). Test the text that code computes: plural forms, conditional messages, validation errors and formatted dates. If the app has translation files, add one CI check that every locale holds every key.

When the answer changes

  • The code chooses between texts, or builds them from numbers and dates.
  • The product ships in more than one language.
  • A lawyer or a regulator prescribes the wording.

Real incident + Code example

The tone-of-voice update that failed 61 tests

On a team calendar product I worked on, component tests asserted 212 strings copied from the English JSX. A tone-of-voice update changed 38 of them and failed 61 tests, and updating them took a developer most of a day. Nothing checked the locale files, and a release showed the raw key calendar.empty.title to German users for nine days, until a customer sent a screenshot. We deleted the copy assertions, kept the role queries, and added two tests like these (Vitest, jest-dom, flat locale files):

import { render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
import en from '../locales/en.json';
import de from '../locales/de.json';
import { WeekSummary } from './WeekSummary';

test.each([
  [0, 'No events this week'],
  [1, '1 event this week'],
  [4, '4 events this week'],
])('describes a week with %i events', (count, text) => {
  render(<WeekSummary eventCount={count} />);
  expect(screen.getByRole('status')).toHaveTextContent(text);
});

test('the German file has every English key', () => {
  expect(Object.keys(de).sort()).toEqual(Object.keys(en).sort());
});

The first test fails when a plural form or the empty state breaks, the second when a German key is missing. A reworded heading fails neither.

FAQ

Should text contents be tested in React tests?

Test the text that a React component computes from props or state, such as plural forms, and skip static copy written literally in the JSX. Find elements by role and accessible name, so wording enters tests only where users rely on it to find a control.

Should I test translations and i18n keys?

Yes, run one CI check that every key of the source language exists in every locale file. The check takes minutes to write and catches raw keys on screen that a team reading one language would miss.

How do I stop copy changes from breaking my tests?

Delete assertions whose only job is to check static wording, and find elements by role with a case-insensitive regular expression such as /save/i. Keep exact text only in tests of computed text, where a changed output is changed behaviour.

Should I test error messages in the UI?

Yes, test that validation errors show their messages, because they appear only on invalid input that the author rarely types. One component test that submits invalid values and finds each message by its key words covers the main path.