skip to content

In a component test, what does asserting a control's exact accessible name — for example with jest-dom's toHaveAccessibleName — give you that a rule-based axe run does not?

level: middleimportance: should knowfreq 28%

answer

  1. rules ask exists, tests ask which
  2. non-empty is a low bar
  3. the name is what users hear and say
  4. guards the choice, does not make it

basics

~20 s

A rule check only asks whether a control has some non-empty name; a name assertion pins which name. That catches wrong, stale or meaningless names — the ones a rule check passes — and locks the string users actually hear and speak.

solid answer

~50 s

The rule engine's naming rules are presence checks: a button with `aria-label="button"`, or one still labelled "Submit form" after the copy changed to "Save", satisfies them completely. Asserting the exact accessible name turns a presence check into a contract — this control is called "Delete item", and any refactor that changes it, drops the label, or reorders the sources it is computed from fails the test. That matters because the name is the string a screen-reader user hears and a voice-control user says out loud, so it is user-facing text, not an implementation detail. I use the same idea for descriptions where hint or error text is wired to a field. What automation still cannot tell me is whether the name is a *good* name — "Save" versus "Submit form" is a human call, and the assertion only guards the choice once it has been made.

code

jsx · 8 lines
jsx
import { render, screen } from '@testing-library/react';
import IconButton from './IconButton';

test('icon button exposes the label it was given', () => {
  render(<IconButton icon="trash" label="Delete item" />);
  // A naming rule passes on any non-empty string; this pins the string.
  expect(screen.getByRole('button')).toHaveAccessibleName('Delete item');
});

go deeper

for a junior

Know that a rule check only asks whether a control has a name at all, and that asserting the exact name is what catches a wrong or leftover label.

for a middle

Explain that the accessible name is computed from several sources, so it can change while the visuals stay identical — and that asserting the resulting string, not the attribute that produced it, is the durable guard.

for a senior

Show judgment about where the assertion belongs: shared components where a wrong name multiplies across screens, components whose job is composing labels, and cases where the query did not already pin the name. Call out the redundancy when it did.

for a principal

Frame the name as user-facing copy under the same review discipline as visible text, and decide where that contract is enforced across a library so every consumer inherits it instead of re-testing it.

## Presence versus identity The rules an accessibility engine can automate for naming all have the same shape: does this element end up with a non-empty accessible name? That question is decidable from the markup, so it automates cleanly — and it stops at exactly the point where the interesting failures begin. All of these pass a naming rule: ```html <button aria-label="button">×</button> <button aria-label="Submit form">Save</button> <button aria-label="Delete">Archive</button> ``` The first is meaningless, the second contradicts the visible label, the third is actively wrong. The engine sees three non-empty strings and reports no violation. An assertion on the *value* of the name is what separates "has a name" from "has the right name". ## Why the name is user-facing text It helps to argue this the way you would argue for asserting on visible copy. The accessible name is what a screen reader announces when the control receives focus. It is also what voice-control software matches when a user says "click Save" — which is why a mismatch between the visible label and the computed name does not merely confuse, it removes the ability to operate the control by voice at all. Treating that string as untested implementation detail is the same mistake as never asserting on button text. And because the name is *computed* from several possible sources rather than read from one attribute, it is unusually easy to change by accident. Adding an attribute, wrapping the content, hiding an inner element, or moving text into an icon can all change the announced string while the visible rendering looks identical — and a rule check stays green through every one of them. ## What the assertion looks like The jest-dom family provides matchers for this directly: ```js render(<IconButton icon="trash" label="Delete item" />); expect(screen.getByRole('button')).toHaveAccessibleName('Delete item'); ``` There is a companion for descriptive text — `toHaveAccessibleDescription` — which is the assertion to reach for when a field has a permanent format hint or a validation message wired to it: it checks that the supporting text actually reaches the control rather than merely sitting near it visually. ## When it is redundant, and when it earns its place Be honest about the overlap. If the test already finds the element by role *and* name, the name has effectively been asserted — a wrong name means the query finds nothing and the test fails on its own. Adding a separate matcher immediately afterwards is duplication. The explicit assertion earns its place when: - The element is obtained some other way — it is the only control of its kind, it came from a container-scoped lookup, or the test is checking a control it did not need to interact with. - The name is the thing under test. A component whose whole job is composing a label from an icon plus a prop plus fallback text deserves a test per branch, and reading a list of expected names in one place documents the contract better than the same strings scattered through queries. - The name is computed from multiple sources and you want a regression guard on the precedence outcome, without asserting on any particular attribute. - It is a shared component. A name that is wrong once in a leaf screen is a bug; a name that is wrong in a library component is the same bug on every screen that consumes it. ## What it still does not buy you Two limits are worth stating out loud, because interviewers listen for them. First, the assertion guards a decision but does not make one. Whether the control should be called "Save", "Submit form" or "Save draft" is a copy and usability judgment; the test only ensures the chosen answer stops silently drifting. Second, a correct name says nothing about whether the control is operable, reachable, focus-visible, or announced at a useful moment. Naming is one property among many, and a component with perfect names can still be unusable by keyboard. ## The reusable principle The generalisation is worth keeping past this specific matcher: automated rules verify that a required property *exists*; tests verify that it has the *value* your product promised. Wherever a rule engine checks presence, ask whether the value is user-facing — and if it is, assert on it the way you would assert on any other visible copy.

  • If the test already queries by role and name, is the extra assertion redundant?
    Usually yes — a wrong name makes that query find nothing, so the test fails anyway and the matcher adds noise. The explicit assertion earns its keep when the element was obtained another way, when composing the name is the component's actual job and you want the branches documented in one place, or when you want a regression guard on a name computed from several sources.
  • What is the equivalent assertion for supporting text like a format hint or validation error?
    An accessible-description assertion. It checks that the hint or error is genuinely associated with the control rather than just positioned near it visually — the failure mode where sighted users see the message and screen-reader users never hear it. It is the same presence-versus-value argument applied to description instead of name.
  • Why is a name/visible-label mismatch worse than a merely vague name?
    Because it breaks voice control outright. A user saying "click Save" on a button whose computed name is "Submit form" gets no match at all — the control becomes unreachable rather than just poorly described. A vague name is a comprehension cost; a mismatched name is a functional failure.
  • Does asserting the exact name make the test brittle when copy changes?
    It makes it fail when user-facing copy changes, which is the point — the same as asserting on visible button text. If a copy change is intended, updating one expected string is cheap and reviewable. Brittleness would be asserting on which attribute produced the name; asserting on the resulting string tracks what the user experiences.

saying these in an interview costs you the question

  • Assumes a passing naming rule means the name is correct
  • Treats the accessible name as internal implementation detail
  • Thinks any non-empty aria-label is good enough
  • Believes automation can judge whether a name is meaningful
  • Ignores that a mismatched name breaks voice control

context