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.