mirror of
https://github.com/ptarmiganlabs/butler-sos.git
synced 2025-12-19 17:58:18 -05:00
Add comprehensive testing framework documentation
Co-authored-by: mountaindude <1029262+mountaindude@users.noreply.github.com>
This commit is contained in:
323
docs/testing-framework.md
Normal file
323
docs/testing-framework.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# Testing Framework Guide for Butler SOS
|
||||
|
||||
This document provides a comprehensive guide for Butler SOS developers on how the testing framework is set up, how to write tests, and how to run them effectively.
|
||||
|
||||
## Overview
|
||||
|
||||
Butler SOS uses **Jest v30.0.5** as its primary testing framework. The project is configured to work with ES modules and provides comprehensive test coverage across all major components.
|
||||
|
||||
## Test Framework Configuration
|
||||
|
||||
### Jest Configuration
|
||||
|
||||
The Jest configuration is defined in `jest.config.mjs` with the following key settings:
|
||||
|
||||
- **Test Environment**: `node` - All tests run in a Node.js environment
|
||||
- **ES Module Support**: Fully configured for ES modules with `type: "module"` in package.json
|
||||
- **Coverage Collection**: Enabled by default with V8 coverage provider
|
||||
- **Coverage Directory**: `./coverage/`
|
||||
- **Clear Mocks**: Automatically clears mocks between tests
|
||||
|
||||
### Package.json Scripts
|
||||
|
||||
The following npm scripts are available for testing:
|
||||
|
||||
```bash
|
||||
# Run Jest tests only
|
||||
npm run jest
|
||||
|
||||
# Run full test suite (Jest + Snyk + formatting)
|
||||
npm run test
|
||||
|
||||
# Run only unit tests (alias for jest)
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
### ES Module Configuration
|
||||
|
||||
Butler SOS runs Jest with specific Node.js flags to support ES modules:
|
||||
|
||||
```bash
|
||||
node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js
|
||||
```
|
||||
|
||||
This configuration:
|
||||
- Enables experimental VM modules support
|
||||
- Suppresses warnings for experimental features
|
||||
- Allows Jest to work seamlessly with ES modules
|
||||
|
||||
## Test File Organization
|
||||
|
||||
### Directory Structure
|
||||
|
||||
Tests are organized using the `__tests__` directory pattern throughout the codebase:
|
||||
|
||||
```
|
||||
src/
|
||||
├── lib/
|
||||
│ └── __tests__/
|
||||
│ ├── heartbeat.test.js
|
||||
│ ├── post-to-mqtt.test.js
|
||||
│ ├── config-file-schema.test.js
|
||||
│ └── ...
|
||||
├── lib/config-schemas/
|
||||
│ └── __tests__/
|
||||
│ ├── destinations.test.js
|
||||
│ ├── credentials.test.js
|
||||
│ └── ...
|
||||
├── lib/udp_handlers/user_events/
|
||||
│ └── __tests__/
|
||||
│ └── message-event.test.js
|
||||
└── plugins/
|
||||
└── __tests__/
|
||||
├── sensible.test.js
|
||||
└── support.test.js
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- Test files use the `.test.js` extension
|
||||
- Test files are named after the module they test (e.g., `heartbeat.js` → `heartbeat.test.js`)
|
||||
- Test files are placed in `__tests__` directories alongside or near the code they test
|
||||
|
||||
## Writing Tests
|
||||
|
||||
### Basic Test Structure
|
||||
|
||||
All test files follow this ES module import pattern:
|
||||
|
||||
```javascript
|
||||
import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
||||
import { moduleToTest } from '../module-to-test.js';
|
||||
|
||||
describe('module-to-test', () => {
|
||||
test('should do something', () => {
|
||||
// Test implementation
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Mocking Dependencies
|
||||
|
||||
Butler SOS uses Jest's ES module mocking capabilities. Here are the common patterns:
|
||||
|
||||
#### External Dependencies
|
||||
|
||||
```javascript
|
||||
// Mock external modules before importing them
|
||||
jest.unstable_mockModule('axios', () => ({
|
||||
default: {
|
||||
get: jest.fn(),
|
||||
post: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Import after mocking
|
||||
const axios = (await import('axios')).default;
|
||||
```
|
||||
|
||||
#### Internal Modules
|
||||
|
||||
```javascript
|
||||
// Mock internal globals module
|
||||
jest.unstable_mockModule('../../globals.js', () => ({
|
||||
default: {
|
||||
logger: {
|
||||
error: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
verbose: jest.fn(),
|
||||
},
|
||||
config: {
|
||||
get: jest.fn(),
|
||||
has: jest.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const globals = (await import('../../globals.js')).default;
|
||||
```
|
||||
|
||||
### Configuration Testing
|
||||
|
||||
Many tests verify configuration schema validation:
|
||||
|
||||
```javascript
|
||||
import Ajv from 'ajv';
|
||||
import addKeywords from 'ajv-keywords';
|
||||
import addFormats from 'ajv-formats';
|
||||
import configFileSchema from '../config-file-schema.js';
|
||||
|
||||
describe('config-file-schema', () => {
|
||||
let ajv;
|
||||
|
||||
beforeEach(() => {
|
||||
ajv = new Ajv({
|
||||
strict: true,
|
||||
allErrors: true,
|
||||
allowUnionTypes: true,
|
||||
});
|
||||
addKeywords(ajv, ['transform']);
|
||||
addFormats(ajv);
|
||||
});
|
||||
|
||||
test('should validate against minimal config', () => {
|
||||
const minimalConfig = { /* config object */ };
|
||||
const validate = ajv.compile(configFileSchema);
|
||||
const valid = validate(minimalConfig);
|
||||
|
||||
expect(valid).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Running All Tests
|
||||
|
||||
```bash
|
||||
# Run all tests with coverage
|
||||
npm run test
|
||||
|
||||
# Run only Jest tests
|
||||
npm run jest
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
### Running Specific Tests
|
||||
|
||||
```bash
|
||||
# Run tests matching a pattern
|
||||
npx jest heartbeat
|
||||
|
||||
# Run tests in a specific directory
|
||||
npx jest src/lib/__tests__/
|
||||
|
||||
# Run a specific test file
|
||||
npx jest src/lib/__tests__/heartbeat.test.js
|
||||
```
|
||||
|
||||
### Running Tests with Options
|
||||
|
||||
```bash
|
||||
# Run tests in watch mode
|
||||
npx jest --watch
|
||||
|
||||
# Run tests with verbose output
|
||||
npx jest --verbose
|
||||
|
||||
# Run tests with coverage report
|
||||
npx jest --coverage
|
||||
```
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### Coverage Configuration
|
||||
|
||||
Coverage is automatically collected during test runs and includes:
|
||||
|
||||
- **Provider**: V8 (native Node.js coverage)
|
||||
- **Output Directory**: `./coverage/`
|
||||
- **Report Format**: Multiple formats including HTML, text, and LCOV
|
||||
|
||||
### Viewing Coverage Reports
|
||||
|
||||
After running tests, coverage reports are available in:
|
||||
|
||||
```
|
||||
coverage/
|
||||
├── lcov-report/
|
||||
│ └── index.html # Detailed HTML coverage report
|
||||
├── lcov.info # Machine-readable coverage data
|
||||
└── coverage-final.json
|
||||
```
|
||||
|
||||
Open `coverage/lcov-report/index.html` in a browser to view detailed coverage information.
|
||||
|
||||
## Jest v30 Specific Considerations
|
||||
|
||||
### ES Module Support
|
||||
|
||||
Jest v30 has improved ES module support but requires specific configuration:
|
||||
|
||||
1. **No `--runInBand` Flag**: Jest v30 has stricter ES module handling when running tests in band mode. Tests run in parallel by default.
|
||||
|
||||
2. **Dynamic Imports**: All dynamic imports (`import()`) in test files are strictly validated.
|
||||
|
||||
3. **Module Mocking**: Use `jest.unstable_mockModule()` for ES module mocking before importing modules.
|
||||
|
||||
### Breaking Changes from v29
|
||||
|
||||
- Removed `--runInBand` from npm scripts due to ES module compatibility issues
|
||||
- Stricter dynamic import validation
|
||||
- Improved parallel test execution performance
|
||||
|
||||
## Test Categories
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Located throughout the codebase, these test individual modules:
|
||||
|
||||
- **Configuration validation** (`config-*.test.js`)
|
||||
- **Data processing** (`post-to-*.test.js`)
|
||||
- **Event handling** (`*-event.test.js`)
|
||||
- **Utility functions** (`heartbeat.test.js`, `service_uptime.test.js`)
|
||||
|
||||
### Integration Tests
|
||||
|
||||
Tests that verify module interactions:
|
||||
|
||||
- **Plugin loading** (`plugins/__tests__/`)
|
||||
- **Configuration schemas** (`config-schemas/__tests__/`)
|
||||
- **Event message handling** (`udp_handlers/__tests__/`)
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Test Organization
|
||||
|
||||
1. **Keep tests close to code**: Place tests in `__tests__` directories near the code they test
|
||||
2. **One test file per module**: Each module should have a corresponding test file
|
||||
3. **Descriptive test names**: Use clear, descriptive test and describe block names
|
||||
|
||||
### Mocking Strategy
|
||||
|
||||
1. **Mock external dependencies**: Always mock HTTP clients, databases, and external services
|
||||
2. **Mock global state**: Mock the globals module for consistent test isolation
|
||||
3. **Use beforeEach for setup**: Initialize mocks and test data in beforeEach blocks
|
||||
|
||||
### Test Structure
|
||||
|
||||
1. **Arrange, Act, Assert**: Structure tests with clear setup, execution, and verification phases
|
||||
2. **Test edge cases**: Include tests for error conditions and boundary cases
|
||||
3. **Keep tests focused**: Each test should verify one specific behavior
|
||||
|
||||
### Performance
|
||||
|
||||
1. **Parallel execution**: Jest v30 runs tests in parallel by default for better performance
|
||||
2. **Selective test runs**: Use Jest's pattern matching to run only relevant tests during development
|
||||
3. **Coverage optimization**: Focus coverage collection on areas under active development
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Module not found errors**: Ensure all imports use correct file extensions (`.js`)
|
||||
2. **ES module import errors**: Check that mocks are set up before imports
|
||||
3. **Timeout issues**: Increase Jest timeout for long-running async tests
|
||||
|
||||
### Debug Tips
|
||||
|
||||
1. **Use `--verbose` flag**: Get detailed output for failing tests
|
||||
2. **Add `console.log`**: Temporary debugging in test files
|
||||
3. **Run single tests**: Isolate failing tests to debug specific issues
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
Tests run automatically in CI/CD pipelines with:
|
||||
|
||||
- Full test suite execution
|
||||
- Coverage report generation
|
||||
- Security scanning with Snyk
|
||||
- Code formatting validation
|
||||
|
||||
The test suite must pass before any code can be merged to main branches.
|
||||
Reference in New Issue
Block a user