Skip to main content
Back to Elite Events

Elite Events Documentation

Technical documentation, guides, and API references for the Elite Events platform.

Development Guides/Testing Guide

Testing Guide

Overview

This guide explains the testing infrastructure, how to write tests, and best practices for maintaining code quality.

Testing Stack

  • Jest: Unit and integration test runner
  • React Testing Library: Component testing
  • MSW (Mock Service Worker): HTTP request mocking
  • @testing-library/jest-dom: DOM matchers

Running Tests

Run all tests

npm test

Run tests in watch mode

npm run test:watch

Generate coverage report

npm run test:coverage

Run specific test file

npm test -- cart-slice.test

Run tests matching pattern

npm test -- --testNamePattern="addItemToCart"

Test Structure

Tests are organized in __tests__ directories parallel to source files:

src/
├── redux/
│   └── features/
│       ├── __tests__/
│       │   └── cart-slice.test.ts
│       └── cart-slice.ts
├── lib/
│   ├── __tests__/
│   │   └── validation.test.ts
│   └── validation-schemas.ts
└── components/
    └── features/
        └── shop/
            └── ProductItem/
                ├── __tests__/
                │   └── ProductItem.test.tsx
                └── index.tsx

Writing Tests

Redux/Reducer Tests

Test Redux slices using direct reducer calls:

import { cart, addItemToCart, removeItemFromCart } from "@/redux/features/cartSlice";

describe("Cart Slice", () => {
  const initialState = { items: [] };

  it("should add item to cart", () => {
    const item = { id: 1, title: "Product", price: 100, quantity: 1 };
    const state = cart.reducer(initialState, addItemToCart(item));

    expect(state.items).toHaveLength(1);
    expect(state.items[0]).toEqual(item);
  });
});

Key Points:

  • Use reducer() function directly for unit testing
  • Test state mutations and pure functions
  • Cover edge cases and error scenarios

Validation/Schema Tests

Test Zod schemas using safeParse():

import { signupSchema } from "@/lib/validation-schemas";

describe("signupSchema", () => {
  it("should validate correct data", () => {
    const result = signupSchema.safeParse({
      email: "test@example.com",
      password: "password123",
      passwordConfirm: "password123",
    });

    expect(result.success).toBe(true);
  });

  it("should reject invalid email", () => {
    const result = signupSchema.safeParse({
      email: "invalid",
      password: "password123",
      passwordConfirm: "password123",
    });

    expect(result.success).toBe(false);
  });
});

Key Points:

  • Test both success and failure cases
  • Validate error messages
  • Test edge cases (empty strings, negative numbers, etc.)

Component Tests

Test components using React Testing Library:

import { render, screen } from "@testing-library/react";
import ProductItem from "@/components/features/shop/ProductItem";

describe("ProductItem", () => {
  const mockProduct = {
    id: 1,
    title: "Test Product",
    price: 100,
    discountedPrice: 80,
  };

  it("should render product title", () => {
    render(<ProductItem item={mockProduct} />);
    expect(screen.getByText("Test Product")).toBeInTheDocument();
  });

  it("should display prices", () => {
    render(<ProductItem item={mockProduct} />);
    expect(screen.getByText("$80")).toBeInTheDocument();
  });
});

Key Points:

  • Test user-visible behavior, not implementation
  • Use screen queries instead of container
  • Mock Next.js Image component
  • Wrap components with necessary providers (Redux, SessionProvider)

API Route Tests

Test API endpoints directly:

import { GET } from "@/app/api/products/route";
import { NextRequest } from "next/server";

describe("GET /api/products", () => {
  it("should return products list", async () => {
    const request = new NextRequest(
      new URL("http://localhost:3000/api/products")
    );

    const response = await GET(request);
    const data = await response.json();

    expect(response.status).toBe(200);
    expect(Array.isArray(data)).toBe(true);
  });
});

Key Points:

  • Test with NextRequest directly
  • Verify response status and format
  • Test error cases and validation
  • Mock database calls as needed

Mocking

Mocking Modules

jest.mock("@/lib/api/products", () => ({
  getProducts: jest.fn(),
}));

Mocking Next.js Features

Already configured in jest.setup.ts:

  • next/router and next/navigation
  • next-auth/react session
  • next/image component

MSW (Mock Service Worker)

For integration tests with HTTP requests:

// jest.setup.ts configures MSW
// Define handlers in src/mocks/handlers.ts
// Use in component tests that make API calls

Coverage Goals

  • Overall: 60%+ coverage
  • Critical features (auth, cart, checkout): 90%+
  • Utilities: 85%+
  • Components: 70%+

Coverage Report

Generate coverage report:

npm run test:coverage

View detailed report:

open coverage/lcov-report/index.html  # macOS
xdg-open coverage/lcov-report/index.html  # Linux

Best Practices

✅ DO

  • Test behavior, not implementation
  • Use descriptive test names
  • Test happy path and edge cases
  • Keep tests focused and isolated
  • Use factories for test data
  • Test error handling
  • Clean up after tests (clear mocks)

❌ DON'T

  • Test implementation details
  • Create overly specific assertions
  • Share state between tests
  • Mock too much (test integration instead)
  • Write tests that are brittle/flaky
  • Ignore async/await issues
  • Leave console.log in tests

Test Examples

Example 1: Reducer Test

describe("removeAllItemsFromCart", () => {
  it("should clear cart with multiple items", () => {
    const item1 = { id: 1, title: "P1", price: 100, discountedPrice: 80, quantity: 1 };
    const item2 = { id: 2, title: "P2", price: 200, discountedPrice: 150, quantity: 1 };

    let state = cart.reducer(initialState, addItemToCart(item1));
    state = cart.reducer(state, addItemToCart(item2));
    expect(state.items).toHaveLength(2);

    state = cart.reducer(state, removeAllItemsFromCart());
    expect(state.items).toHaveLength(0);
  });
});

Example 2: Validation Test

describe("signupSchema", () => {
  it("should reject non-matching passwords", () => {
    const result = signupSchema.safeParse({
      email: "test@example.com",
      password: "password123",
      passwordConfirm: "password456",
    });

    expect(result.success).toBe(false);
  });
});

Example 3: Component Test

describe("ProductItem", () => {
  it("should display discount percentage", () => {
    const product = {
      id: 1,
      title: "Product",
      price: 100,
      discountedPrice: 80,
    };

    render(<ProductItem item={product} />);
    // Calculate: (1 - 80/100) * 100 = 20%
    expect(screen.getByText("20%")).toBeInTheDocument();
  });
});

Debugging Tests

Run single test

npm test -- cart-slice.test.ts

Run in debug mode

node --inspect-brk node_modules/.bin/jest --runInBand

Then open chrome://inspect in Chrome

import { render, screen } from "@testing-library/react";

it("should render element", () => {
  const { debug } = render(<Component />);
  debug(); // Prints rendered HTML
});

Continuous Integration

GitHub Actions automatically runs tests on:

  • Push to main or develop branches
  • Pull requests to main or develop

Workflows defined in .github/workflows/:

  • test.yml: Run tests and build
  • lint.yml: Run linting and type checking

Tests must pass before merging to main.

Performance Testing

Monitor test execution time:

npm test -- --verbose

Common Issues

Test timeout

Increase Jest timeout:

jest.setTimeout(10000);

it("slow test", async () => {
  // ...
}, 10000);

Module not found

Check path aliases in jest.config.ts match tsconfig.json:

moduleNameMapper: {
  "^@/(.*)$": "<rootDir>/src/$1",
}

Async/await issues

Use waitFor for async operations:

import { waitFor } from "@testing-library/react";

it("should load data", async () => {
  render(<Component />);

  await waitFor(() => {
    expect(screen.getByText("Data loaded")).toBeInTheDocument();
  });
});

Resources

Next Steps

  1. Write tests for critical features
  2. Aim for 60%+ overall coverage
  3. Monitor coverage in CI/CD
  4. Review and refactor tests regularly
  5. Keep tests maintainable and focused
Documentation | Elite Events | Philip Rehberger