-
Notifications
You must be signed in to change notification settings - Fork 0
Testing Guide
This document outlines the testing practices and guidelines for the AUCC project.
Our project uses Vitest as the testing framework. Tests are written using TypeScript and follow the Jest-like syntax.
To run tests, use one of the following commands:
# Run tests in watch mode (development)
pnpm test
# Run tests with coverage report
pnpm test:coverageTests 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
Each test file should follow this structure:
- Import necessary testing utilities and the code being tested
- Mock external dependencies
- Group related tests using
describeblocks - Write individual test cases using
itblocks
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
})
})
})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()
})When writing test cases, follow these principles:
-
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) })
-
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')
-
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')
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,
})
})
})- Isolation: Each test should be independent and not rely on the state of other tests
- Meaningful Assertions: Make specific assertions that verify the exact behavior you're testing
-
Clean Setup/Teardown: Use
beforeEachandafterEachto ensure a clean test environment - Mock External Dependencies: Always mock external services, databases, and APIs
- Test Coverage: Aim for high test coverage but prioritize meaningful tests over coverage percentage
it('should handle async operations', async () => {
const result = await asyncFunction()
expect(result).toBeDefined()
})it('should handle errors', async () => {
const error = new Error('Failed')
mockService.mockRejectedValue(error)
await expect(asyncFunction()).rejects.toThrow('Failed')
})it('should handle empty input', async () => {
const result = await processInput([])
expect(result).toEqual([])
})Common issues and their solutions:
-
Tests not running: Ensure the test file follows the
.test.tsor.spec.tsnaming convention - Mocks not working: Verify that mocks are set up before the code being tested is imported
-
Async test failures: Make sure to use
async/awaitor return promises in async tests