Testing Guide for KLA Digital Services¶
Overview¶
This guide explains how to write and run tests for services in the KLA Digital monorepo. All services must maintain ≥80% test coverage across lines, functions, branches, and statements.
Test Structure¶
Unit Tests¶
Unit tests should be placed alongside the code they test with the naming convention *.test.ts or *.spec.ts.
Example structure:
services/my-service/
├── src/
│ ├── index.ts
│ ├── index.test.ts
│ ├── utils/
│ │ ├── helper.ts
│ │ └── helper.test.ts
Integration Tests¶
Integration tests should use the naming convention *.integration.test.ts and test real interactions between components.
Writing Tests¶
Basic Test Template¶
import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
describe('MyComponent', () => {
let originalEnv: NodeJS.ProcessEnv;
beforeEach(() => {
// Save original state
originalEnv = { ...process.env };
// Setup test environment
jest.clearAllMocks();
});
afterEach(() => {
// Restore original state
process.env = originalEnv;
});
it('should perform expected behavior', () => {
// Arrange
const input = 'test';
// Act
const result = myFunction(input);
// Assert
expect(result).toBe('expected');
});
});
Mocking Best Practices¶
- Mock external dependencies - Always mock external services, APIs, and modules
- Use jest.mock() - Place mocks at the top of the test file
- Clear mocks between tests - Use
jest.clearAllMocks()in beforeEach - Verify mock calls - Use
expect(mockFn).toHaveBeenCalledWith()
Example:
// Mock modules before imports
jest.mock('@opentelemetry/api');
jest.mock('./external-service');
import { trace } from '@opentelemetry/api';
import { externalService } from './external-service';
describe('Service with mocks', () => {
const mockTrace = trace as jest.Mocked<typeof trace>;
const mockService = externalService as jest.Mocked<typeof externalService>;
beforeEach(() => {
jest.clearAllMocks();
});
it('should call external service', async () => {
mockService.getData.mockResolvedValue({ data: 'test' });
const result = await myFunction();
expect(mockService.getData).toHaveBeenCalledTimes(1);
expect(result).toEqual({ data: 'test' });
});
});
Running Tests¶
Commands¶
# Run all tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverage
# Run integration tests only
pnpm test:integration
# Run tests for a specific service
cd services/my-service && pnpm test
Coverage Requirements¶
The CI pipeline enforces the following coverage thresholds: - Lines: ≥80% - Functions: ≥80% - Branches: ≥80% - Statements: ≥80%
Coverage reports are generated in services/*/coverage/ directories.
Policy Testing¶
Cerbos Policy Tests¶
Cerbos policies should have corresponding test files with the _test.yaml suffix.
Example test structure:
name: PolicyTestSuite
description: Tests for my_resource policy
tests:
- name: test_user_can_read
input:
actions: ["read"]
resource:
kind: "my_resource"
id: "123"
principal:
id: "user1"
roles: ["viewer"]
expected:
- action: "read"
effect: EFFECT_ALLOW
Kyverno Policy Tests¶
Kyverno policies should have test files in policies/kyverno/tests/ with the -test.yaml suffix.
Example test structure:
apiVersion: cli.kyverno.io/v1alpha1
kind: Test
metadata:
name: my-policy-test
policies:
- ../my-policy.yaml
resources:
- ../test-resources/valid-resource.yaml
- ../test-resources/invalid-resource.yaml
results:
- policy: my-policy
rule: my-rule
resource: valid-resource
result: pass
- policy: my-policy
rule: my-rule
resource: invalid-resource
result: fail
Debugging Tests¶
Run specific test file¶
pnpm test path/to/test.ts
Run tests matching pattern¶
pnpm test --testNamePattern="should handle errors"
Debug with VS Code¶
Add this configuration to .vscode/launch.json:
{
"type": "node",
"request": "launch",
"name": "Jest Debug",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "--no-cache", "${file}"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
Common Pitfalls¶
- Forgetting to restore state - Always restore environment variables and global state
- Not mocking timers - Use
jest.useFakeTimers()for time-dependent tests - Incomplete async handling - Always await promises and use async/await
- Not testing error cases - Test both success and failure scenarios
- Over-mocking - Only mock what's necessary; prefer integration tests when possible
Adding Tests to New Services¶
When creating a new service:
- Copy the
jest.config.jsfrom an existing service - Add test scripts to
package.json - Create initial test files for all modules
- Run coverage to ensure ≥80% threshold
- Add to CI pipeline if needed
Example jest.config.js:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
testPathIgnorePatterns: ['/node_modules/', '\\.integration\\.test\\.ts$'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.test.{ts,tsx}',
'!src/**/__tests__/**',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};