Skip to main content

UI-kit Component Testing Guide

This guide helps you confidently write, extend, and refactor tests in our UI-kit component library. Whether you're implementing a new component, writing edge-case coverage, or investigating a broken test, this page provides structure, best practices, and real examples.


Test environment setup

All component tests should live next to their component in the same folder and follow the naming convention: ComponentName.spec.tsx.

We use React Testing Library with Jest for writing tests.

To run all tests:

npm test

To run a specific test file:

npm test -- src/components/MyComponent/MyComponent.spec.tsx

Choose your testing scenario

Pick the situation that brought you here:

  1. I'm building a brand new component
  2. I'm testing a new edge case
  3. Some existing tests broke after my changes
  4. I'm just refactoring tests

Tests for brand-new component

When creating a new component, begin thinking about tests from the start — not after the implementation is "done."

Ask yourself:

  • What is the component’s core responsibility?
  • What props, state, or interaction could break?
  • How will users interact with it?
  • Are there validations, animations, or side effects?

If you're refreshing the browser over and over hoping something changes... that's your cue to write a test instead 🥲.

Tests for new edge-cases

This is the easiest case: you already know what to test. Your job now is figuring out the cleanest way to test it.

  • Is the bug reproducible via props, state, or interaction?
  • Can you isolate it to just one piece of logic?
  • What’s the expected vs. actual behavior?

The more focused your test is, the easier it’ll be to debug or refactor later.

Old tests fail

This one’s tricky. A test that used to pass is now failing. You’ll need to investigate:

  • Is the test wrong, or is it catching a real regression?
  • Did you change behavior without updating the test?
  • Are the test queries too brittle?

Once you’ve figured it out, fix or rewrite the test — and follow the best practices in How to write tests.

Tests refactoring

Maybe you’ve got time. Maybe you’re eliminating tech debt. Either way, good on you 👏

Use this time to:

  • Replace brittle queries with semantic RTL queries
  • Remove duplicate logic
  • Extract repeated setup into beforeEach
  • Apply any missing coverage from new edge cases

Refer back to:


How to write tests

Querying elements

We use React Testing Library (RTL) for writing tests. Avoid using low-level DOM APIs like querySelector. RTL queries are built to encourage accessibility and maintainability.

Always prefer RTL queries (getByRole, getByLabelText, etc.) over getByTestId or raw DOM APIs.

RTL query types

Query priority

Follow this priority from most recommended to least:

  1. Accessible queries (preferred)
  • getByRole
  • getByLabelText
  • getByPlaceholderText
  • getByText
  • getByDisplayValue
  1. Semantic queries
    • getByAltText
    • getByTitle
  2. Test IDs (last resort)
    • getByTestId

If you must use data-testid, pass it via the testing environment only — not the production component.


Best practices by test type

Disabled state

Check that the primary interactive element is disabled:

const input = getByTestId("main-input");
expect(input).toBeDisabled();

Ideally, test all interactive elements inside a disabled component.

Validation state

Use aria-invalid to confirm error state and pair it with error message assertions:

const input = getByLabelText("Last Name");
const error = getByRole("alert");

fireEvent.change(input, { target: { value: "INVALID" } });

expect(input).toHaveAttribute("aria-invalid", "true");
expect(error).toHaveTextContent("Should contain only English characters");

Focus on behavior, not implementation

When writing tests for components, prioritize what the component does, not how it looks or how it’s built internally.

info

If your test fails because of a class name or internal element structure change — that’s usually a sign the test is too tightly coupled to the implementation.

✅ Test this:

  • What happens when a user interacts with the component?
  • Does validation logic trigger on input?
  • Is the correct event fired on button click?
  • Is the component disabled when expected?

🚫 Don’t test this:

  • Specific class names or element tags
  • DOM structure that comes from UI Kit internals
  • Inline styles or layout-specific CSS
// GOOD: test what the component does
const button = getByRole("button", { name: /submit/i });
fireEvent.click(button);
expect(mockSubmit).toHaveBeenCalled();

// BAD: test how the component is built
const span = container.querySelector(".btn-primary > span");
expect(span).toBeVisible(); // this may break if internal structure changes

If you find yourself writing a test that feels fragile, take a step back and ask: “Would this still pass if the UI Kit team changed how this component is rendered, but not how it behaves?”

That’s a great way to check if your test is too dependent on internals.