Configuring Testing Environment - Implementing various testing strategies for multiple apps

Testing in frontend development has evolved far beyond simple unit tests. Modern interfaces are complex, interactive, and integrated with APIs — meaning your testing strategy needs to validate both UI behavior and user experience across layers.

In this post, we’ll explore how to implement testing strategies for frontend applications, focusing on tools and practices that help ensure your product’s quality, stability, and confidence before release.


1. Unit Testing

Goal: Test individual UI units — functions, hooks, or components — in isolation.

Unit tests are the foundation of frontend testing. They ensure your logic and rendering behave as expected without depending on the DOM or browser.

Common tools:

Example (React + Vitest):

// Button.tsx
export function Button({ label, onClick }: { label: string; onClick: () => void }) {
  return <button onClick={onClick}>{label}</button>;
}

// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

test('renders button and handles click', () => {
  const handleClick = vi.fn();
  render(<Button label="Click me" onClick={handleClick} />);
  fireEvent.click(screen.getByText('Click me'));
  expect(handleClick).toHaveBeenCalledTimes(1);
});

Best practices:

2. Integration Testing

Goal: Verify that multiple UI components work together correctly.

Integration tests validate how parts of your app interact — for example, a form component that updates global state and triggers navigation.

Example (React + React Router + RTL):


import { render, screen, fireEvent } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from './App';

test('navigates to dashboard after successful login', async () => {
  render(
    <MemoryRouter initialEntries={['/login']}>
      <App />
    </MemoryRouter>
  );

  fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'user@example.com' } });
  fireEvent.change(screen.getByLabelText(/password/i), { target: { value: '123456' } });
  fireEvent.click(screen.getByRole('button', { name: /login/i }));

  expect(await screen.findByText(/Welcome, user/i)).toBeInTheDocument();
});

Best practices :

3. End-to-End (E2E) Testing

Goal: Simulate real user behavior in a real browser.

E2E tests validate your frontend as a whole: routes, interactions, form submissions, and navigation. They help ensure that what users actually experience works correctly.

Common tools:

Playwright Cypress Puppeteer

Example (Playwright):


import { test, expect } from '@playwright/test';

test('user can log in and see dashboard', async ({ page }) => {
  await page.goto('http://localhost:3000/login');
  await page.fill('input[name=email]', 'user@example.com');
  await page.fill('input[name=password]', 'password123');
  await page.click('button[type=submit]');
  await expect(page).toHaveURL(/dashboard/);
  await expect(page.getByText('Welcome, user')).toBeVisible();
});

Best practices:

4. Visual Regression Testing

Goal: Catch unintended visual or layout changes automatically.

Visual regression testing compares screenshots across builds to detect pixel-level changes in components or pages.

Tools:

Chromatic (for Storybook) Percy Playwright snapshot comparison

Example (Chromatic):

Add your Storybook stories. Chromatic runs them in the cloud and highlights visual diffs between versions.

When to use:

In design system or component library development. When refactoring CSS or UI themes.

5. Component Snapshot Testing

Goal: Capture UI structure snapshots for quick regression checks.

Snapshot testing is useful for components that render predictable static output.

Example (Jest/Vitest):

import { render } from '@testing-library/react';
import { Header } from './Header';

test('matches snapshot', () => {
  const { container } = render(<Header title="Dashboard" />);
  expect(container).toMatchSnapshot();
});

Use snapshots sparingly — they’re easy to overuse and hard to maintain if your UI changes often.

6. Mocking APIs & Network Requests

Most frontend apps depend on APIs. To make tests reliable, mock network calls.

Tools:

Example (MSW):

import { rest } from 'msw';
import { setupServer } from 'msw/node';

export const server = setupServer(
  rest.get('/api/user', (_, res, ctx) => {
    return res(ctx.json({ name: 'John Doe' }));
  })
);

Why it matters:

Dito

© 2025 Ditorahard

Instagram 𝕏 GitHub