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:
- I'm building a brand new component
- I'm testing a new edge case
- Some existing tests broke after my changes
- 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:
- Accessible queries (preferred)
getByRolegetByLabelTextgetByPlaceholderTextgetByTextgetByDisplayValue
- Semantic queries
getByAltTextgetByTitle
- 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.
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.