Skip to content

Testing Guide

David Zhu edited this page Apr 23, 2025 · 3 revisions

This document outlines the testing practices and guidelines for the AUCC project.

Getting Started

Our project uses Vitest as the testing framework. Tests are written using TypeScript and follow the Jest-like syntax.

Running Tests

To run tests, use one of the following commands:

# Run tests in watch mode (development)
pnpm test

# Run tests with coverage report
pnpm test:coverage

Test Structure

Tests are organized following the source code structure. For each source file, there should be a corresponding test file in a __tests__ directory at the same level.

Example:

src/
  queries/
    rivers.ts
    __tests__/
      rivers.test.ts

Writing Tests

Test File Organization

Each test file should follow this structure:

  1. Import necessary testing utilities and the code being tested
  2. Mock external dependencies
  3. Group related tests using describe blocks
  4. Write individual test cases using it blocks

Example:

import { beforeEach, describe, expect, it, vi } from 'vitest'

import { myFunction } from '../myFile'

describe('Module Name', () => {
  describe('function name', () => {
    it('should do something specific', () => {
      // test implementation
    })
  })
})

Mocking

We use Vitest's mocking capabilities to isolate the code being tested:

// Mock external dependencies
vi.mock('@/lib/someModule', () => ({
  someFunction: vi.fn(),
}))

// Reset mocks between tests
beforeEach(() => {
  vi.clearAllMocks()
})

Test Cases

When writing test cases, follow these principles:

  1. Arrange-Act-Assert: Structure your tests in three distinct phases

    it('should do something', () => {
      // Arrange: Set up test data and conditions
      const mockData = {
        /* ... */
      }
    
      // Act: Execute the code being tested
      const result = someFunction(mockData)
    
      // Assert: Verify the results
      expect(result).toBe(expectedValue)
    })
  2. Test Description: Write clear, descriptive test names that explain the expected behavior

    // Good
    it('should return null when user is not found')
    
    // Bad
    it('test user null')
  3. Edge Cases: Include tests for edge cases and error conditions

    it('should handle empty input')
    it('should handle invalid parameters')
    it('should handle error responses')

Example: Testing Queries

Here's an example of testing a query function:

describe('River queries', () => {
  const mockPayloadClient = {
    find: vi.fn(),
  }

  beforeEach(() => {
    vi.clearAllMocks()
    ;(getPayloadClient as any).mockResolvedValue(mockPayloadClient)
  })

  it('should fetch rivers with default parameters', async () => {
    // Arrange
    const mockResponse = {
      docs: [{ id: '1', name: 'Test River' }],
      hasNextPage: false,
      nextPage: null,
      totalDocs: 1,
    }
    mockPayloadClient.find.mockResolvedValue(mockResponse)

    // Act
    const result = await getRivers()

    // Assert
    expect(mockPayloadClient.find).toHaveBeenCalledWith({
      collection: 'rivers',
      page: 1,
      limit: 10,
      sort: 'grade',
    })
    expect(result).toEqual({
      rivers: mockResponse.docs,
      hasNextPage: mockResponse.hasNextPage,
      nextPage: mockResponse.nextPage,
      totalDocs: mockResponse.totalDocs,
    })
  })
})

Best Practices

  1. Isolation: Each test should be independent and not rely on the state of other tests
  2. Meaningful Assertions: Make specific assertions that verify the exact behavior you're testing
  3. Clean Setup/Teardown: Use beforeEach and afterEach to ensure a clean test environment
  4. Mock External Dependencies: Always mock external services, databases, and APIs
  5. Test Coverage: Aim for high test coverage but prioritize meaningful tests over coverage percentage

Common Patterns

Testing Async Functions

it('should handle async operations', async () => {
  const result = await asyncFunction()
  expect(result).toBeDefined()
})

Testing Error Cases

it('should handle errors', async () => {
  const error = new Error('Failed')
  mockService.mockRejectedValue(error)

  await expect(asyncFunction()).rejects.toThrow('Failed')
})

Testing Edge Cases

it('should handle empty input', async () => {
  const result = await processInput([])
  expect(result).toEqual([])
})

Troubleshooting

Common issues and their solutions:

  1. Tests not running: Ensure the test file follows the .test.ts or .spec.ts naming convention
  2. Mocks not working: Verify that mocks are set up before the code being tested is imported
  3. Async test failures: Make sure to use async/await or return promises in async tests

Clone this wiki locally