skip to content

Testing

You will learn how frontend code actually gets tested: the Testing Library family, what a component test should assert, how to fake the network, and how to catch visual and accessibility regressions. Interviewers dig here because most candidates can name a test runner but cannot explain what makes a UI test survive a refactor.

on this pageshow

explore

questions

117 · 7 sections

A React component renders a user's name only after a fetch promise resolves. In a React Testing Library test, which query do you use to assert that the name appeared, and what must you do with the value that query returns?

level: juniorimportance: must knowfreq 78%
basics
~20 s

Use an async findBy* query, such as screen.findByText('Ada Lovelace'), and await it. It returns a promise that retries the query until the element appears or a default 1000 ms timeout expires; unawaited, you assert on a promise instead of the DOM.

open as a page

A React component test suite queries almost every element with screen.getByTestId(...). Why does React Testing Library treat test ids as a last resort, and what does a test-id-only suite fail to catch?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Test ids are invisible to users, so a test-id-only suite proves markup exists rather than that the interface works. Querying by role, label or visible text makes the query itself verify the text and accessible name a real user relies on.

open as a page

React Testing Library documents a recommended priority order for its queries. What is that order, and why does getByRole sit at the top while getByTestId sits at the bottom?

level: juniorimportance: must knowfreq 80%
basics
~10 s

Queries are ranked by how closely they mirror how a real person finds a control: getByRole with an accessible name first, then getByLabelText, getByPlaceholderText, getByText and getByDisplayValue, then getByAltText and getByTitle, and getByTestId last.

open as a page

In Testing Library, what is the difference between fireEvent.click(button) and userEvent.click(button), and why is user-event the recommended default?

level: juniorimportance: must knowfreq 76%
basics
~20 s

fireEvent dispatches one synthetic DOM event. user-event replays the whole sequence a real interaction produces — pointer, mouse, focus and click events — and refuses to interact with elements a user could not touch, so it catches bugs fireEvent silently passes.

open as a page

A React Testing Library test logs "An update to Profile inside a test was not wrapped in act(...)". What is that warning actually reporting, and what in the test usually causes it?

level: middleimportance: must knowfreq 68%
basics
~20 s

The warning reports that a component's state updated while React was outside an act() scope — almost always a promise resolving after the test's synchronous body finished. React Testing Library already wraps render, events and its async utilities in act, so the real fix is awaiting the UI change the promise causes.

open as a page

In a Vue 3 component test written with Vue Testing Library, why does `await fireEvent.click(getByRole('button', { name: 'Add' }))` need the await before you assert that the new item is on screen?

level: juniorimportance: must knowfreq 58%
basics
~20 s

Vue applies DOM updates asynchronously on the next tick rather than during the event handler. Vue Testing Library's fireEvent returns a promise that waits for that flush, so skipping the await asserts against the old DOM.

open as a page

Vue Testing Library is built on top of Vue Test Utils. Compared with calling Vue Test Utils' `mount()` directly, what does it deliberately stop your test from touching, and why is that treated as a feature rather than a limitation?

level: middleimportance: must knowfreq 62%
basics
~20 s

Vue Testing Library's render() hands back DOM queries instead of a Vue Test Utils wrapper, so there is no component instance, no setData, and no CSS-selector find to assert on. A test can only touch rendered output, which is what survives refactoring.

open as a page

A Vue 3 component's entire contract is emitting a `submit` event with the form payload — it renders no result of its own. How do you test that with Vue Testing Library, and does asserting on emitted events contradict its user-centric principle?

level: middleimportance: should knowfreq 48%
basics
~20 s

Drive the real form through its accessible controls, then assert on the recorded event: the render result exposes emitted(), keyed by event name with the arguments of each emission. An emitted event is the component's public contract with its parent, not an internal detail.

open as a page

A Vue 3 component you want to test with Vue Testing Library uses a router link, a store, and a value supplied through `provide`/`inject`. How do you get it rendering without reaching into its internals, and what do you fake versus supply for real?

level: seniorimportance: should knowfreq 44%
basics
~20 s

Supply the dependencies through render's mounting options — plugins for the router and store, provide for injected values — rather than reaching into the instance. Give real instances where you can, and fake only what crosses the network or cannot run in the test environment.

open as a page

The react-test-renderer package renders a React component without any DOM. What does `TestRenderer.create(<Profile />).toJSON()` return, and why would a project want a renderer that produces that instead of DOM nodes?

level: juniorimportance: should knowfreq 32%
basics
~20 s

toJSON() returns a plain JavaScript object tree of the host elements React produced — each node has type, props and children, with text as plain strings. No DOM node is created, so the renderer runs anywhere React runs, including React Native.

open as a page

A React 19 web app's test suite still renders components with the react-test-renderer package, and CI now prints a deprecation warning for it. Why was that renderer deprecated for web testing, and what do you move those tests to?

level: middleimportance: should knowfreq 44%
basics
~20 s

react-test-renderer renders to fake host objects rather than to a DOM, so web tests never exercise the environment users get. React 19 deprecates it and logs a warning; move web tests to @testing-library/react and React Native tests to @testing-library/react-native.

open as a page

A React component suite uses the react-test-renderer package and triggers behaviour by calling props on the returned tree, such as `renderer.root.findByType(SaveButton).props.onClick()`. Every test is green while the Save button is unusable in the browser. Which classes of defect can a renderer with no DOM never catch?

level: seniorimportance: should knowfreq 30%
basics
~20 s

Anything the host owns stays invisible: real event dispatch and bubbling, disabled or covered controls, focus and keyboard operation, form semantics, CSS-driven visibility and layout, and the accessibility tree. Calling a prop bypasses every gate a real user would have to pass.

open as a page

Under the react-test-renderer package, a React component whose effect runs `inputRef.current.focus()` blows up on a null ref, though the same component works in the browser. Why is the ref null there, and how do you get such a component to render under that renderer?

level: middleimportance: nice to knowfreq 18%
basics
~20 s

There is no DOM node to attach, so react-test-renderer sets host refs to null by default. Pass the createNodeMock option to TestRenderer.create to return a stand-in object per host element — but a mocked focus() proves nothing about real focus behaviour.

open as a page

Your component test suite runs an automated accessibility rule check on every component and reports zero violations. What can you honestly claim from that, and which accessibility problems does it leave undetected?

level: middleimportance: must knowfreq 55%
basics
~20 s

Zero violations only means no enabled rule found a machine-decidable markup defect. Automated rules cannot judge whether names and alt text are meaningful, whether focus order and visibility work, or whether announcements arrive usefully — those need manual keyboard and screen-reader checks.

open as a page

In a UI component test, what counts as an implementation detail, and what concrete rule do you use to decide whether a given assertion will survive a refactor?

level: middleimportance: must knowfreq 76%
basics
~20 s

An implementation detail is anything a user cannot perceive: internal state, render counts, DOM structure, class names, which child components exist. The working rule is to assert only what a user would notice if it stopped being true.

open as a page

You have a custom React hook `useCart` that several components use. When is it right to test it directly with React Testing Library's `renderHook`, and when should you test it through a component that consumes it?

level: middleimportance: must knowfreq 62%
basics
~20 s

Test a hook directly when its return value is the product: a shared hook whose callers are other developers. Test it through a consuming component when the hook exists to serve one component, because there the rendered output is what people depend on.

open as a page

In a component test written with Testing Library and Jest or Vitest, how does a snapshot assertion such as expect(container).toMatchSnapshot() differ from a targeted assertion such as expect(screen.getByRole('heading')).toHaveTextContent('Invoice'), and when does the snapshot stop earning its place?

level: middleimportance: must knowfreq 65%
basics
~20 s

A snapshot asserts that the whole serialized output is identical to a recording; a targeted assertion states one thing a human decided must be true. Snapshots catch every change including irrelevant ones, so once they fail routinely for reasons unrelated to behaviour, they stop being evidence and become a rubber stamp.

open as a page

In a frontend component test, what does an automated accessibility check such as jest-axe's axe(container) followed by expect(results).toHaveNoViolations() actually do, and what does a passing result prove?

level: juniorimportance: should knowfreq 40%
basics
~20 s

An automated accessibility check evaluates a fixed set of rules against the component's rendered DOM and fails the test if any rule is violated. Passing means no enabled rule fired — not that the component is accessible.

open as a page

An end-to-end suite signs in through the login form at the start of every test, and a full run now takes twenty minutes. Why is UI login per test a poor default, and what do end-to-end suites do instead?

level: juniorimportance: must knowfreq 74%
basics
~20 s

Driving the login form repeats a slow, flake-prone flow that adds no coverage after the first test. Authenticate once through the app's auth API instead, save the resulting cookies and browser storage as a session, and start every test already signed in.

open as a page

An end-to-end browser test clicks a button using the CSS selector `.card > div:nth-child(2) > button.btn-primary`. It passes today and fails after a refactor that changed only markup nesting and class names, with no change to what a user can do. What makes that selector fragile, and what should the test anchor on instead?

level: juniorimportance: must knowfreq 76%
basics
~20 s

A structural CSS path couples the test to DOM nesting and styling class names, which change freely without changing behaviour. Anchor on what a user perceives — the element's role and visible name — or on an explicit test-id attribute the component deliberately exposes.

open as a page

In a browser end-to-end test, why is inserting a fixed sleep (such as Playwright's page.waitForTimeout(500) or Cypress's cy.wait(500)) before an assertion a bad way to handle timing, and what do you use instead?

level: juniorimportance: must knowfreq 78%
basics
~20 s

A fixed sleep hard-codes a guess about duration: too short and the test fails on a slow machine, too long and every run pays the delay. Wait on a retrying condition instead, which returns the moment the expected state appears.

open as a page

In an end-to-end browser test suite, what belongs inside a page object and what should stay in the test — and why do experienced teams keep assertions out of page objects?

level: middleimportance: must knowfreq 65%
basics
~20 s

A page object owns a screen's locators and user actions and returns data or locators; the test keeps the assertions and the scenario. Assertions buried in a shared helper hide what each test actually checks and make one test's expectations everyone's.

open as a page

In an end-to-end browser test, why do many teams locate elements by accessible role and visible name — for example a button named "Save changes" — before trying any other strategy, and where does that strategy break down?

level: middleimportance: must knowfreq 64%
basics
~20 s

Role plus name is how a user identifies a control, so it changes only when the interface genuinely changes, and a test that cannot find the element usually reveals a real labelling gap. It breaks on unlabelled icon controls, non-semantic markup, duplicated names, and copy or locale churn.

open as a page

A component fetches a list from an API, and every test in its suite stubs that call with a successful response. What is left untested, and how would you write a test that covers the failure path?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Happy-path stubs leave the error and empty branches unexecuted, so bugs there ship unnoticed. Override the stub in one test so the endpoint fails, then assert the visible error message appears and the list does not.

open as a page

In Mock Service Worker (MSW) v2 a request handler is written as http.get('/api/users', resolver). What are the two halves of that handler, what must the resolver return, and does the component's own fetch() code have to change?

level: juniorimportance: must knowfreq 55%
basics
~20 s

An MSW handler pairs a predicate (method plus path, e.g. http.get('/api/users')) with a resolver function that returns a Response, usually built with HttpResponse.json(). Application code is untouched — MSW answers the real request at the network boundary.

open as a page

A test stubs a component's API call with an instantly-resolved response, so its loading skeleton disappears before any assertion can run. How do you make the pending state observable in a test without adding a fixed sleep?

level: middleimportance: must knowfreq 58%
basics
~20 s

Take control of when the stubbed response settles: either inject a delay into the handler, or hand the test a promise it resolves by hand. Assert the skeleton while the request is pending, release the response, then assert it is replaced by the data.

open as a page

Your frontend tests stub API responses with hand-written JSON fixtures. How do you make the build fail when the backend's OpenAPI or GraphQL schema renames a response field, and where does that protection stop?

level: middleimportance: must knowfreq 58%
basics
~20 s

Generate types from the API schema as a build step and annotate every fixture with the generated type, so a renamed or removed field fails typecheck. That only catches shape changes present in the schema copy you last regenerated — never runtime values or semantics.

open as a page

Why can a frontend test that mocks the application's own API-client module stay green while the feature is broken for real users, and what changes when the fake is moved down to the network boundary instead?

level: middleimportance: must knowfreq 62%
basics
~20 s

Mocking your own client deletes the code that talks to the server, so the test can only confirm your assumptions about it. Faking at the network boundary keeps that code running and replaces only the server, so request shape, parsing and error handling are exercised for real.

open as a page

A visual regression suite covers 40 pages across 3 browser engines, 4 viewport widths, 2 colour schemes and 2 device-pixel-ratio settings. How many baseline images is that, and what does each extra axis cost beyond image storage?

level: middleimportance: must knowfreq 60%
basics
~20 s

Forty-eight configurations per page, so 1,920 baselines — the axes multiply rather than add. The real cost is not disk: it is CI time, a multiplied flake surface, and above all the number of images a human must review and approve on every intentional design change.

open as a page

A dashboard screenshot test fails on every run: the page shows "updated 3 minutes ago", a randomly generated order ID, and a third-party map tile. How do you make each of those stable, and when would you mask a region instead?

level: middleimportance: must knowfreq 58%
basics
~20 s

Make the data deterministic at its source: freeze the clock and pin the timezone and locale, seed or stub the ID generator, and serve fixed fixtures. Mask only what you truly cannot control, such as a live third-party tile, because a mask is a permanent blind spot.

open as a page

In a visual regression test, a card plays a 300 ms entry animation before it settles. Why is inserting a fixed delay before the screenshot a poor fix, and what would you do instead?

level: middleimportance: must knowfreq 68%
basics
~20 s

A fixed delay is a guess: on a loaded CI machine the shutter still lands mid-animation, and on a fast one it only wastes time. Remove the motion instead — inject test-only CSS that disables animations and transitions and hides the text caret.

open as a page

In a visual regression suite, why does requiring two screenshots of the same page to match exactly fail almost immediately, and what does a perceptual per-pixel comparison do instead?

level: middleimportance: must knowfreq 58%
basics
~20 s

Byte-equal screenshots almost never repeat: GPU rasterisation, text shaping and anti-aliasing shift pixel values without changing what a human sees. Visual tools instead decode both images, score each pixel pair with a perceptual colour distance, and count only differences above a tolerance.

open as a page

Screenshot baselines captured on a developer's macOS laptop fail on the Linux CI runner with tiny anti-aliasing differences across every piece of text. Why does that happen, and how would you set the suite up so baselines are portable?

level: seniorimportance: must knowfreq 52%
basics
~20 s

Text rasterization is operating-system specific — hinting, anti-aliasing and the installed fallback fonts all differ — so a macOS baseline is simply not valid on Linux. Produce and compare baselines inside one pinned container image, used identically in CI and on developer machines.

open as a page