All Articles
Tutorial10 min read

Testing JavaScript Applications: A Practical Approach

Stop writing tests that break on every refactor. Learn unit, integration, and E2E testing that actually provides value.

T

TechGyanic

November 29, 2025

Testing JavaScript Applications: A Practical Approach

I used to hate writing tests. They felt like busywork that slowed down development. Then I joined a team with good tests and realized—they're not about catching bugs. They're about confidence to ship.

The Testing Pyramid

    /\  E2E (Few)
   /  \
  /____\ Integration
 /      \
/________\ Unit Tests (Many)
  • Unit tests: Fast, isolated, test single functions
  • Integration tests: Test modules working together
  • E2E tests: Test complete user flows

Unit Testing with Jest

// utils/formatCurrency.js
export function formatCurrency(amount, currency = 'INR') {
  return new Intl.NumberFormat('en-IN', {
    style: 'currency',
    currency
  }).format(amount);
}

// utils/formatCurrency.test.js
import { formatCurrency } from './formatCurrency';

describe('formatCurrency', () => {
  test('formats INR correctly', () => {
    expect(formatCurrency(1000)).toBe('₹1,000.00');
  });

  test('formats large numbers with lakhs notation', () => {
    expect(formatCurrency(150000)).toBe('₹1,50,000.00');
  });

  test('handles USD', () => {
    expect(formatCurrency(1000, 'USD')).toBe('$1,000.00');
  });

  test('handles zero', () => {
    expect(formatCurrency(0)).toBe('₹0.00');
  });
});

Testing React Components

// components/Counter.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

describe('Counter', () => {
  test('renders initial count', () => {
    render(<Counter initialCount={5} />);
    expect(screen.getByText('Count: 5')).toBeInTheDocument();
  });

  test('increments on button click', () => {
    render(<Counter initialCount={0} />);
    
    const button = screen.getByRole('button', { name: /increment/i });
    fireEvent.click(button);
    
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });
});

Integration Testing

Test how modules work together:

// api/users.test.js
import request from 'supertest';
import app from '../app';
import { db } from '../database';

describe('User API', () => {
  beforeEach(async () => {
    await db.users.deleteMany();
  });

  test('POST /api/users creates user and returns 201', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ email: '[email protected]', name: 'Test User' });

    expect(response.status).toBe(201);
    expect(response.body.user.email).toBe('[email protected]');
    
    // Verify in database
    const dbUser = await db.users.findUnique({
      where: { email: '[email protected]' }
    });
    expect(dbUser).not.toBeNull();
  });

  test('returns 400 for invalid email', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ email: 'invalid-email', name: 'Test' });

    expect(response.status).toBe(400);
  });
});

Mocking

// Don't hit real APIs in tests
jest.mock('./services/emailService');

import { sendEmail } from './services/emailService';

test('sends welcome email on registration', async () => {
  sendEmail.mockResolvedValue({ success: true });
  
  await registerUser({ email: '[email protected]' });
  
  expect(sendEmail).toHaveBeenCalledWith({
    to: '[email protected]',
    template: 'welcome'
  });
});

E2E Testing with Playwright

// tests/checkout.spec.js
import { test, expect } from '@playwright/test';

test('complete checkout flow', async ({ page }) => {
  // Go to product page
  await page.goto('/products/123');
  
  // Add to cart
  await page.click('button:has-text("Add to Cart")');
  
  // Go to checkout
  await page.click('a:has-text("Checkout")');
  
  // Fill form
  await page.fill('#email', '[email protected]');
  await page.fill('#phone', '9876543210');
  
  // Complete order
  await page.click('button:has-text("Place Order")');
  
  // Verify success
  await expect(page.locator('h1')).toHaveText('Order Confirmed');
});

What to Test

Do test:

  • Business logic
  • Edge cases
  • Error handling
  • User interactions

Don't test:

  • Implementation details
  • Third-party libraries
  • Obvious code

Testing Philosophy

Write tests that give you confidence, not tests that give you 100% coverage.

Coverage metrics can be misleading. A few well-written integration tests often provide more value than dozens of unit tests.

Start with the highest-risk code and work your way out.

testingjavascriptjestreactquality
Share this article
T

Written by

TechGyanic

Sharing insights on technology, software architecture, and development best practices.