Jest & React Testing Library (RTL) – Simplified

Abheeshta PAbheeshta P
5 min read

Testing is not just about verifying if code runs. It’s about verifying that your app behaves correctly from the user’s perspective. Jest + React Testing Library (RTL) gives a powerful combination to achieve this.

1. Filtering Tests in Watch Mode

When running Jest in watch mode, you can filter which tests run. This saves time when working on specific files or scenarios.

  • By file name (p) → only run tests from matching file(s).

  • By test name (t) → run tests that match a string/regex in the test/describe name.

  • All tests (a) → run the entire suite.

  • Only changed files → default mode, runs tests related to changed files.

👉 Outside watch mode, you can use test.only and test.skip to explicitly include/exclude tests.

2. Grouping Tests with describe

Jest provides the describe(name, fn) function to group related tests.

  • Helps structure tests into logical blocks.

  • describe.only and describe.skip work just like test.only and test.skip.

  • Nested describe blocks allow hierarchical organization.

  • Each file is considered one test suite.

Example:

describe('Login Component', () => {
  test('renders login form', () => { /* ... */ });
  test('shows error for invalid input', () => { /* ... */ });
});

3. Code Coverage

Jest can measure how much of your code is covered by tests.

Run with:

npm test -- --coverage --watchAll --collectCoverageFrom="src/**/*.{ts,tsx}"

Key metrics:

  • Branches → if/else conditions tested

  • Functions → all functions executed

  • Lines → all lines run

  • Statements → overall execution

You can enforce minimum thresholds in package.json:

"jest": {
  "coverageThreshold": {
    "global": {
      "branches": 80,
      "functions": 85,
      "lines": 90,
      "statements": 90
    }
  }
}

👉 Useful for CI/CD pipelines where you want to ensure minimum test quality.

4. What to Test vs What Not to Test

Test:

  • Component rendering

  • Rendering with props

  • Different states (loading, error, success)

  • User events (clicks, typing, etc.)

Don’t Test:

  • Implementation details (internal state logic)

  • Third-party library behavior

  • Things outside the user’s perspective

5. React Testing Library (RTL) Queries

RTL encourages testing like a real user. Instead of testing internals, you query elements as they appear in the DOM.

Common Queries

  • getByRole (preferred)
screen.getByRole('textbox', { name: /username/i })
  • getByLabelText → finds input by <label> text.

  • getByPlaceholderText → finds input by placeholder.

  • getByText → matches text inside p, div, span, etc.

  • getByDisplayValue → finds input by its current value.

  • getByAltText → for images.

  • getByTitle → tooltips or elements with title.

  • getByTestId → fallback with data-testid attribute.

👉 getAllBy* queries return multiple elements, so you may need .toHaveLength() assertions.

Matching Text

Queries support different matchers:

  • String"Login" (exact match)

  • Regex/login/i (ignore case)

  • Function(content) => content.startsWith('Hello')

Negative Queries

Use queryBy* when you expect absence (so test won’t throw if not found).

expect(screen.queryByText(/loading/i)).not.toBeInTheDocument()

Async Queries

Use findBy* for elements that appear asynchronously (default 1s timeout).

await screen.findByText(/welcome/i)

6. Debugging Helpers

  • screen.debug() → prints current DOM snapshot.

  • logRoles(element) → lists accessible roles of elements.

  • Testing Playground browser extension can help find best queries.

7. User Events

Simulating interactions with @testing-library/user-event:

  • Clickawait user.click(button)

  • Typingawait user.type(input, "Hello")

  • Tab navigationawait user.tab()

  • Clear inputawait user.clear(input)

  • Select dropdownawait user.selectOptions(select, "optionValue")

  • Upload fileawait user.upload(fileInput, file)

  • Clipboard actionscopy, paste, etc.

👉 Prefer userEvent over fireEvent as it simulates more realistic user interactions.

8. Testing Custom Hooks

RTL provides renderHook for hooks:

const { result } = renderHook(() => useCounter(), {
  initialProps: { initialValue: 5 }
});

expect(result.current.count).toBe(5);

9. Mocking

Example handler:

rest.get('/posts', (req, res, ctx) => {
  return res(ctx.status(200), ctx.json([{ id: 1, title: 'Mock Post' }]))
})

Useful for testing without hitting real APIs.

10. Static Analysis & Code Quality

Before tests, ensure code is consistent and clean.

  • ESLint → catch mistakes & enforce style

  • Prettier → consistent formatting

  • Husky + lint-staged → run checks before committing

  • TypeScript → type safety

Example setup:

npx husky-init && npm install
npm install --save-dev lint-staged prettier eslint

Add pre-push hook to run tests:

npx husky add .husky/pre-push "npm test -- --watchAll=false"

🔑 Key Takeaways

  • Use describe/test to organize tests logically.

  • Filter tests in watch mode for faster dev workflow.

  • Focus on user behavior, not implementation details.

  • Use RTL queries that match how users find elements.

  • Ensure code coverage thresholds in CI/CD.

  • Mock APIs & use static analysis tools for clean, reliable code.


Bonus

Why React Testing Library (RTL)?

  • Focus on user behavior → RTL encourages testing components the way a real user would interact with them (clicks, typing, text on screen), instead of testing implementation details.

  • Less fragile tests → Since it avoids testing private internals (like class names or state), your tests don’t break easily when refactoring.

  • Simple API → Queries like getByText, getByRole, etc., make tests readable and closer to natural language.

  • Community standard → RTL has become the go-to library for React testing, widely adopted in industry.

👉 Example: Instead of checking if a button has onClick, you check if the button is clickable and what happens after click.

Why Jest?

  • All-in-one framework → Provides test runner, assertion library, mocking, and snapshot testing out of the box.

  • Zero config for JS/TS → Easy to start, especially with React, Node.js, or frontend projects.

  • Fast & isolated → Runs tests in parallel and isolates them for reliability.

  • Great ecosystem → Works seamlessly with RTL for unit & integration tests.

👉 Example: Write test("adds numbers", () => expect(sum(2,3)).toBe(5)) and Jest handles everything — runner, assertion, report.

Why Playwright?

  • End-to-End (E2E) testing → Unlike Jest/RTL (which test components in isolation), Playwright tests full apps in real browsers (Chromium, Firefox, WebKit).

  • Cross-browser automation → Ensures app works the same across browsers and devices.

  • Modern features → Auto-waiting, screenshots, videos, and parallel execution for stable E2E tests.

  • CI/CD friendly → Works well in pipelines to catch production-level issues.

👉 Example: Open app in browser, click login, enter creds, check if dashboard loads — exactly what a user would do.

Thanks to Codevolution

0
Subscribe to my newsletter

Read articles from Abheeshta P directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abheeshta P
Abheeshta P

I am a Full-stack dev turning ideas into sleek, functional experiences 🚀. I am passionate about AI, intuitive UI/UX, and crafting user-friendly platforms . I am always curious – from building websites to diving into machine learning and under the hood workings ✨. Next.js, Node.js, MongoDB, and Tailwind are my daily tools. I am here to share dev experiments, lessons learned, and the occasional late-night code breakthroughs. Always evolving, always building.