Talently
Talently
Testing Library

Testing Library

The testing library focused on user behavior

Testing Library is a family of JavaScript testing libraries that promotes writing tests from the user's perspective instead of implementation details. Available for React, Vue, Angular, Svelte, and other frameworks, it provides utilities for finding elements as users perceive them, through text, ARIA roles, and labels, making tests more robust and meaningful.

JavaScriptTypeScriptReactTesting

Market demand

Testing Library is the most widely adopted component testing library in the React ecosystem and has high demand in any frontend position that includes testing. It is the standard recommended by the React team for component tests.

Testing standard in React ecosystemHigh demand in frontend positions with testingOfficially recommended by the React team

Technical requirements

Intermediate

Requires mastery of JavaScript or TypeScript and of the component framework used. Familiarity with testing concepts like mocking, assertions, and Jest is essential. Knowledge of web accessibility and ARIA roles facilitates writing tests with the correct selectors.

Use cases

Real Projects

Testing Library is used to develop:

  • Tests of React, Vue, or Angular components in isolation
  • Integration tests of multiple interacting components
  • Tests of custom hooks with renderHook
  • Tests of forms with validations and user flows

Types of Company

Testing Library is adopted by:

  • Frontend teams with React, Vue, or Angular that include testing
  • Companies with TDD or BDD culture on the frontend
  • Startups with test coverage on critical components
  • Organizations with quality and code coverage requirements

Production Scenarios

Testing Library is widely used in production environments such as:

  • Unit tests of components in CI/CD pipelines
  • Regression tests of critical UI components
  • Accessibility tests integrated into the development workflow
  • Tests of complex forms with validations and states

Scalability

Testing Library offers multiple mechanisms to scale applications:

  • Integration with Jest or Vitest for fast execution
  • MSW for mocking APIs in integration tests
  • Custom render with providers for components with contexts
  • Screen.debug for visual DOM debugging in tests

Advantages and Disadvantages

Advantages

Accessibility-based selectors that promote accessible interfaces by design.

Tests resistant to refactoring since they don't depend on implementation details.

Intuitive API with userEvent that simulates real user interactions.

Disadvantages

Initial learning curve for developers accustomed to testing by implementation.

Some advanced use cases require additional configuration of providers and mocks.

Does not cover E2E tests that require a real browser with all browser APIs.

Comparison

Advantages of Enzyme

  • Greater access to component internal details
  • Easier for testing internal state and methods
  • More familiar for legacy React developers

Considerations

Enzyme allows accessing component internals like state and methods, but generates fragile tests that break with refactoring. Testing Library promotes more robust behavior-focused tests, being the currently recommended option.

Basic questions

Tests that verify internal state, function names, or internal component structure break with refactoring even when the visible behavior doesn't change. Testing Library promotes testing what the user sees and can do, making tests more resilient to implementation changes and more meaningful as behavior documentation.
getBy throws an error if the element doesn't exist, being suitable when the element should be present. queryBy returns null if it doesn't exist, being suitable for verifying element absence. findBy returns a Promise and waits for the element to appear, being suitable for elements that appear asynchronously.
Queries by CSS class or id are fragile to style changes or markup refactoring. Queries by getByRole or getByText reflect how users and screen readers perceive the interface, being more resistant to implementation changes and promoting that components are accessible for the tests to work.
userEvent simulates user interactions more realistically by firing all events that would occur in a real interaction like mousedown, focus, keydown, and keyup when typing in an input. fireEvent fires a single synthetic DOM event. userEvent is preferable for generating interactions closer to those of a real user.
Using Mock Service Worker with msw that intercepts requests at the Service Worker level and returns mocked responses, or mocking the fetch or axios module with jest.mock. MSW is preferable because it intercepts real requests without modifying the component code, being closer to real behavior.
Testing Library with Jest or Vitest is faster and suitable for most isolated component tests. Cypress Component Testing is preferable for components that depend on browser APIs that jsdom doesn't implement like IntersectionObserver, ResizeObserver, or Canvas APIs. Testing Library covers 90% of cases with less overhead.
Using renderHook from @testing-library/react which mounts the hook in a minimal test component and exposes its return value. The result can be accessed with result.current and the hook can be updated with act to trigger state changes, verifying the hook's behavior without needing to create a real component to test it.
For component tests and integration tests of multiple components where verifying behavior from the user's perspective is desired, form tests with validations, accessibility tests using role and label queries, and hook tests with renderHook. It is the ideal complement to pure logic unit tests with Jest.

Technical questions

By creating a customRender function that wraps Testing Library's render with the necessary providers like the Redux store, React router, theme provider, or authentication context. This function is exported instead of Testing Library's render and all tests use it, ensuring components have access to their dependencies without repeating configuration.
Using expect(screen.queryByText('text')).not.toBeInTheDocument() with queryBy which returns null instead of throwing an error when the element doesn't exist. Using getBy to verify absence would throw an error before expect can make the assertion, so queryBy is always the correct variant for verifying something is not present.
By wrapping interactions that trigger asynchronous updates in act or using waitFor which retries the assertion until it passes or the timeout expires. With userEvent, interactions are handled automatically with act. findBy also automatically waits for the element to appear without needing explicit waitFor.
Using queries by getByRole with the correct ARIA role and accessible name of the element, verifying that forms have correct labels with getByLabelText, using jest-axe to run automatic accessibility analysis on the rendered component, and verifying that error messages are correctly associated with their inputs via aria-describedby.
By wrapping the component under test in the context's Provider with the desired value for the test within the customRender wrapper, or using the wrapper option in render for components that consume context. To test behavior with different context values, render is called with different Provider values.
By calling screen.debug() which prints the current HTML of the test DOM to the console, or screen.debug(element) to print only a specific element. It is the primary tool for understanding what the component is rendering at the moment of failure and what selectors are available when a query doesn't find the expected element.
By rendering the form, interacting with fields using userEvent.type and userEvent.clear to simulate typing, triggering submit with userEvent.click on the submit button, and verifying with expect that error messages appear with getByText or getByRole for validation messages, and that messages disappear when fields are corrected.
Using jest.mock('module-name') to mock complete modules, jest.spyOn to mock specific methods while keeping the rest of the real module, or mockResolvedValue and mockRejectedValue to control responses from functions that return Promises. For network APIs, MSW is preferred which mocks at the network level without modifying the code.

Advanced questions

By defining behavior tests for each component that verify visible interactions and states, accessibility tests with jest-axe for each component, tests for edge cases like empty states and errors, and integration tests for frequent component combinations. Tests document expected behavior and serve as a contract for each component's public API.
By configuring MSW with handlers that define responses for each endpoint, starting the mock server in setupTests with server.listen(), and resetting handlers in afterEach with server.resetHandlers() to guarantee isolation between tests. In specific tests, handlers can be overridden with server.use() to simulate errors or edge cases without affecting other tests.
By verifying that tests use accessible queries like getByRole and getByLabelText instead of getByTestId or CSS selectors, that tests verify visible behavior instead of internal state, that tests fail for correct reasons by simulating real bugs, that the false positive and false negative ratio is low, and that tests are fast without unnecessary waitFor with arbitrary timeouts.
Using Jest's fake timers with jest.useFakeTimers() to manually advance time and verify state after the animation, or using waitFor to wait for the element to reach the final state after the transition. For entrance animations, waiting for the element to be visible with waitFor is used, and for exit animations, waitForElementToBeRemoved is used.
By migrating test by test starting with components with the most frequent changes to get the benefit of more robust tests sooner, using the available migration codemod for mechanical changes, rewriting tests that access internal state or props to verify visible behavior instead of implementation, and leveraging the migration to improve accessibility of components that don't have accessible queries.
By establishing ESLint rules with eslint-plugin-testing-library that detect anti-patterns like incorrect query usage, reviewing in code review that tests don't access implementation details, writing tests that document expected behavior of each component from the user's perspective, and updating tests when behavior changes intentionally as part of the development process.

Common interview mistakes

Overusing data-testid when accessible queries like getByRole or getByLabelText exist reflects not understanding Testing Library's philosophy. data-testid should be the last resort when no accessible selector is available, not the default. Its excessive use indicates that the component may not be accessible.
Using getBy to verify element absence generates an error instead of a failed assertion, making the error message confusing. Not knowing these variants and their use cases reflects limited experience with Testing Library.
fireEvent fires individual synthetic events that don't faithfully replicate user behavior. userEvent simulates the complete sequence of events that occur in a real interaction. Preferring fireEvent due to not knowing userEvent reflects outdated practice with Testing Library.
Verifying that React state has a certain value or that an internal component function was called breaks the abstraction and generates fragile tests. Testing Library deliberately doesn't expose access to internal state to force visible behavior testing.
Mocking fetch or axios directly with jest.mock instead of using MSW generates more fragile and less realistic mocks. Not knowing MSW as the standard tool for mocking APIs in integration tests reflects being out of date with the JavaScript testing ecosystem.
Repeating provider configuration in each test or having tests that fail because the component needs an unavailable context reflects not having worked on real projects with Testing Library where components always have context dependencies.