Test Isolation in React Testing

Test isolation means each test starts from a known state and cannot affect the result of another test. In React tests, that includes the rendered DOM, mocks, timers, and shared mutable data.

A test is isolated when its result depends on its own setup and the behavior under test, not on which tests ran before it. Isolation makes failures easier to reproduce and lets tests run in a different order or in parallel more safely.

For React Testing Library tests, remove rendered DOM between tests so one test cannot find or interact with another test's elements. React Testing Library can do this automatically when it detects the test runner's lifecycle hooks; explicit `cleanup()` is useful when automatic cleanup is not configured or when you want the teardown to be visible.

Also reset external state that tests change: mock call history and implementations, spies on real methods, fake timers, and mutable fixtures. Choose the cleanup operation to match the state: clearing calls is different from resetting a mock implementation or restoring a spied-on original method.

Prefer creating fresh props, fixtures, and mock instances inside each test or in a per-test setup. Teardown should undo global changes even when assertions fail; avoid relying on test order or on one test to clean up after another.

Key points

Interview questions

What does test isolation mean, and why does it matter?

An isolated test sets up the state it needs and does not depend on another test having run first. It should also leave shared state in a condition that will not affect later tests. This makes failures more reproducible and allows tests to be reordered or run concurrently more safely.

How do you prevent one React Testing Library test's DOM from affecting another?

Remove rendered trees after each test, using React Testing Library's automatic cleanup when the test environment provides supported lifecycle hooks. If automatic cleanup is not configured, call `cleanup()` in teardown. Also keep each test's render and queries scoped to its own setup rather than depending on elements left by another test.

What is the difference between clearing, resetting, and restoring mocks?

Clearing a mock removes recorded calls and results while generally keeping its configured behavior. Resetting also resets its behavior, so the test may need to configure it again. Restoring a spy puts the original method or property back. Pick the operation based on whether the test changed call history, mock behavior, or a real object through a spy.

A test passes by itself but fails when the full suite runs. How would you debug it?

Look for state that crosses test boundaries: DOM nodes, mock implementations, fake timers, global properties, module-level caches, and mutated shared fixtures. Re-run the test with different neighboring tests or in a randomized order if the runner supports it, then inspect setup and teardown for the state that persists. Fix the ownership boundary by creating fresh test data and reliably undoing global changes. Avoid solving the issue by depending on a particular test order.

How should tests that use fake timers avoid affecting other tests?

Enable fake timers only for tests that need them, and return to real timers during teardown. Advance timers deliberately in the test rather than relying on elapsed wall-clock time. If the component schedules work, also consider whether pending timers should be run or discarded before restoring the real timer functions. Keep timer cleanup coordinated with component unmounting so scheduled callbacks do not outlive the test.