Testing Guide
Comprehensive testing is crucial for maintaining the quality and reliability of CI Dokumentor. This guide covers all aspects of testing in the project.
Testing Philosophy
CI Dokumentor follows these testing principles:
- Test-Driven Development - Write tests before or alongside implementation
- Clean Test Code - Tests should be as clean and maintainable as production code
- Fast Feedback - Tests should run quickly to enable rapid development
- Comprehensive Coverage - Aim for high test coverage across all packages
- Real-World Scenarios - Tests should reflect actual usage patterns
Test Stack
CI Dokumentor uses a modern testing stack:
- Vitest - Fast unit testing framework with native TypeScript support
- Mock-fs - File system mocking for end-to-end tests
- InversifyJS Testing - Dependency injection mocking and testing patterns
- @vitest/coverage-v8 - Code coverage reporting
Test Types
Unit Tests
Fast, isolated tests focusing on individual components:
# Run all unit tests
pnpm test
# Run tests for specific package
pnpm nx test core
pnpm nx test cli
pnpm nx test repository-github
# Run tests for specific file
pnpm nx test core src/formatter/markdown/markdown-formatter.adapter.spec.ts
Integration Tests
Tests that verify component interactions and realistic usage patterns:
- End-to-end CLI testing (
.e2e.spec.ts
) - Generator adapter integration tests
- Migration adapter integration tests
Mock Testing
Comprehensive mocking strategies using dependency injection:
// Example: Mocking repository providers
beforeEach(() => {
container
.rebind(TYPES.RepositoryProvider)
.toConstantValue(mockRepositoryProvider);
});
Running Tests
Workspace Commands
# Run all tests across all packages
pnpm test
# Run tests with coverage
pnpm test:ci
# Run tests in watch mode (development)
pnpm nx test core --watch
Package-Specific Commands
# Core package tests
pnpm nx test core
# CLI package tests
pnpm nx test cli
# GitHub Actions package tests
pnpm nx test cicd-github-actions
# Repository providers
pnpm nx test repository-git
pnpm nx test repository-github
Coverage Reports
# Generate coverage reports
pnpm test:ci
# View coverage in browser (after running coverage)
open coverage/index.html
Testing Guidelines
Writing Good Tests
Test Structure (AAA Pattern)
describe("Component", () => {
it("should behavior when condition", () => {
// Arrange - Setup test data and mocks
const input = "test-input";
const expectedOutput = "expected-result";
// Act - Execute the code under test
const result = systemUnderTest.method(input);
// Assert - Verify the results
expect(result).toBe(expectedOutput);
});
});
Descriptive Test Names
Use clear, descriptive test names that explain the behavior:
// ✅ Good: describes behavior and context
it("should throw error when source file does not exist");
// ❌ Bad: vague and unclear
it("should work");
it("test error case");
Test Dependencies and Mocking
Use dependency injection for clean, testable code:
// Example from core package
beforeEach(() => {
container = new Container();
container.bind(TYPES.FormatterAdapter).to(MockFormatterAdapter);
});
File System Testing
Use mock-fs for End-to-end file system operations tests only:
import mockFs from "mock-fs";
beforeEach(() => {
mockFs({
"/test": {
"action.yml": "name: Test Action",
"README.md": "# Test",
},
});
});
afterEach(() => {
mockFs.restore();
});
CI/CD Testing Patterns
Generator Adapter Testing
Test documentation generation with real manifest files:
it("should generate complete documentation", async () => {
// Arrange: Real action.yml content
const actionContent = `
name: My Action
description: Test action
inputs:
test-input:
description: Test input
`;
// Act: Generate documentation
await generator.generateDocumentation({
source: "/test/action.yml",
// ... other parameters
});
// Assert: Verify generated content
const result = readFileSync("/test/README.md", "utf-8");
expect(result).toContain("## Inputs");
});
Migration Testing
Test migration adapters with before/after scenarios:
it("should migrate action-docs markers", async () => {
const before = `
<!-- action-docs-inputs source="action.yml" -->
<!-- action-docs-outputs source="action.yml" -->
`;
const expected = `
<!-- inputs:start -->
<!-- inputs:end -->
<!-- outputs:start -->
<!-- outputs:end -->
`;
const result = await migrationAdapter.migrate(before);
expect(result).toBe(expected);
});
Common Testing Patterns
Repository Provider Testing
describe("GitHubRepositoryProvider", () => {
it("should support GitHub repositories", async () => {
// Mock Git remote URL
mockGitRemote("https://github.com/owner/repo.git");
const supports = await provider.supports();
expect(supports).toBe(true);
});
});
CLI Testing
describe("CLI", () => {
it("should generate documentation with valid options", async () => {
const result = await cli.run([
"generate",
"--source",
"/test/action.yml",
"--dry-run",
]);
expect(result.exitCode).toBe(0);
});
});
Performance Testing
Benchmarking
For performance-critical operations:
it("should parse large action files efficiently", () => {
const start = performance.now();
parser.parse(largeActionContent);
const duration = performance.now() - start;
expect(duration).toBeLessThan(100); // ms
});
Troubleshooting Tests
Common Issues
File System Permissions
// Ensure mock-fs is properly restored
afterEach(() => {
mockFs.restore();
});
Dependency Injection
// Verify container bindings
beforeEach(() => {
container.isBound(TYPES.SomeService)
? container.rebind(TYPES.SomeService)
: container.bind(TYPES.SomeService);
});
Async Operations
// Always await async operations in tests
await expect(async () => {
await service.methodThatThrows();
}).rejects.toThrow("Expected error");
Debug Tests
# Run single test file with debug output
pnpm nx test core --testNamePattern="specific test name"
# Run with verbose output
pnpm nx test core --verbose
Related Documentation
- Setup Guide - Development environment setup
- Architecture - Understanding the codebase structure
- Contributing - Contribution guidelines