Code cartographer! Consul Caesar.js needs a detailed map showing which areas of the fort have been examined and which are still waiting for inspection. Test Coverage is like a detailed map of tested code fragments!
Test Coverage is a metric showing what portion of code is executed during tests. It's like a fort map showing:
1# Running tests with coverage in NestJS
2npm run test:cov
3
4# Result:
5# Statements : 87.5% ( 175/200 )
6# Branches : 83.3% ( 50/60 )
7# Functions : 90.0% ( 45/50 )
8# Lines : 88.0% ( 176/200 )1// tribute-calculator.service.ts
2@Injectable()
3export class TributeCalculatorService {
4 calculateValue(tribute: Tribute): number {
5 let value = tribute.baseValue; // Line 1 ✓
6
7 if (tribute.rarity === 'legendary') { // Line 2 ✓
8 value = value * 10; // Line 3 - NOT COVERED!
9 }
10
11 if (tribute.condition === 'damaged') { // Line 4 ✓
12 value = value * 0.5; // Line 5 ✓
13 }
14
15 return value; // Line 6 ✓
16 }
17}
18
19// Test that doesn't cover all lines
20describe('TributeCalculatorService', () => {
21 it('should calculate value of damaged tribute', () => {
22 const tribute = {
23 baseValue: 1000,
24 rarity: 'common', // We're not testing 'legendary'!
25 condition: 'damaged'
26 };
27
28 const result = service.calculateValue(tribute);
29
30 expect(result).toBe(500);
31 });
32
33 // Missing test for legendary tributes!
34});
35
36// Coverage: 83.3% (5/6 lines)1// navigation.service.ts
2@Injectable()
3export class NavigationService {
4 calculateRoute(weather: Weather, distance: number): RouteRecommendation {
5 // Branch 1: weather check
6 if (weather.condition === 'storm') { // TRUE branch ?
7 return { recommendation: 'stay_in_port', safety: 'dangerous' };
8 }
9
10 // Branch 2: distance check
11 if (distance > 1000) { // TRUE branch ✓, FALSE branch ?
12 return { recommendation: 'long_campaign', safety: 'moderate' };
13 }
14
15 return { recommendation: 'short_trip', safety: 'safe' };
16 }
17}
18
19// Test not covering all branches
20describe('NavigationService', () => {
21 it('should recommend a short trip', () => {
22 const result = service.calculateRoute(
23 { condition: 'calm' }, // storm=FALSE ✓
24 500 // distance<=1000, i.e. FALSE ✓
25 );
26
27 expect(result.recommendation).toBe('short_trip');
28 });
29
30 // Missing tests for:
31 // - weather.condition === 'storm' (TRUE)
32 // - distance > 1000 (TRUE)
33});
34
35// Branch Coverage: 50% (2/4 branches)1// legion.service.ts
2@Injectable()
3export class LegionService {
4 getAllLegionarys(): Legionary[] { // TESTED ✓
5 return this.legionaries;
6 }
7
8 addLegionary(legionaries: Legionary): void { // TESTED ✓
9 this.legionaries.push(legionaries);
10 }
11
12 promoteLegionary(id: string): void { // NOT TESTED!
13 const legionaries = this.findById(id);
14 legionaries.rank = this.getNextRank(legionaries.rank);
15 }
16
17 dismissLegionary(id: string): void { // NOT TESTED!
18 this.legionaries = this.legionaries.filter(p => p.id !== id);
19 }
20
21 private findById(id: string): Legionary { // TESTED indirectly ✓
22 return this.legionaries.find(p => p.id === id);
23 }
24
25 private getNextRank(currentRank: string): string { // NOT TESTED!
26 const ranks = ['Legionary', 'Ballistarius', 'Optio', 'Centurion'];
27 const currentIndex = ranks.indexOf(currentRank);
28 return ranks[currentIndex + 1] || currentRank;
29 }
30}
31
32// Function Coverage: 50% (3/6 functions)1// jest.config.js
2module.exports = {
3 moduleFileExtensions: ['js', 'json', 'ts'],
4 rootDir: 'src',
5 testRegex: '.*\.spec\.ts$',
6 transform: {
7 '^.+\.(t|j)s$': 'ts-jest',
8 },
9 collectCoverageFrom: [
10 '**/*.(t|j)s',
11 '!**/*.spec.ts',
12 '!**/*.e2e-spec.ts',
13 '!**/node_modules/**',
14 '!**/dist/**',
15 '!**/*.interface.ts',
16 '!**/*.dto.ts',
17 '!**/main.ts',
18 ],
19 coverageDirectory: '../coverage',
20 testEnvironment: 'node',
21
22 // Coverage thresholds - minimum requirements
23 coverageThreshold: {
24 global: {
25 branches: 80,
26 functions: 80,
27 lines: 80,
28 statements: 80
29 },
30 // Special requirements for important files
31 './src/tribute/*.ts': {
32 branches: 90,
33 functions: 95,
34 lines: 90,
35 statements: 90
36 },
37 './src/security/*.ts': {
38 branches: 95,
39 functions: 100,
40 lines: 95,
41 statements: 95
42 }
43 },
44
45 // Coverage reporters
46 coverageReporters: ['text', 'lcov', 'html', 'json'],
47};1{
2 "scripts": {
3 "test": "jest",
4 "test:watch": "jest --watch",
5 "test:cov": "jest --coverage",
6 "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
7 "test:cov:watch": "jest --coverage --watch",
8 "test:cov:html": "jest --coverage && open coverage/lcov-report/index.html"
9 }
10}1// Coverage report analysis
2// File: tribute.service.ts
3// Uncovered Lines: 23, 45-48, 67
4
5// Line 23: Error handling
6if (!tribute) {
7 throw new NotFoundException('Tribute not found'); // Not tested!
8}
9
10// Lines 45-48: Edge case
11if (tribute.value < 0) { // Not tested!
12 tribute.value = 0;
13 this.logger.warn('Negative tribute value corrected');
14}
15
16// Line 67: Async error
17catch (error) {
18 this.logger.error('Database error', error); // Not tested!
19 throw new InternalServerErrorException();
20}
21
22// Add missing tests:
23it('should throw exception when tribute does not exist', () => {
24 mockRepository.findOne.mockResolvedValue(null);
25
26 expect(() => service.findById('non-existent'))
27 .rejects.toThrow(NotFoundException);
28});
29
30it('should correct negative tribute value', () => {
31 const tribute = { id: 1, name: 'Test', value: -100 };
32
33 const result = service.validateTribute(tribute);
34
35 expect(result.value).toBe(0);
36 expect(mockLogger.warn).toHaveBeenCalledWith('Negative tribute value corrected');
37});
38
39it('should handle database error', async () => {
40 mockRepository.save.mockRejectedValue(new Error('DB Error'));
41
42 await expect(service.saveTribute(tribute))
43 .rejects.toThrow(InternalServerErrorException);
44
45 expect(mockLogger.error).toHaveBeenCalledWith('Database error', expect.any(Error));
46});1// Complete coverage of validation function
2describe('Legionary Validation', () => {
3 describe('validateLegionaryRank', () => {
4 // Happy path
5 it('should accept a valid rank', () => {
6 expect(validator.validateLegionaryRank('Centurion')).toBe(true);
7 });
8
9 // Edge cases for complete coverage
10 it('should reject null', () => {
11 expect(validator.validateLegionaryRank(null)).toBe(false);
12 });
13
14 it('should reject undefined', () => {
15 expect(validator.validateLegionaryRank(undefined)).toBe(false);
16 });
17
18 it('should reject empty string', () => {
19 expect(validator.validateLegionaryRank('')).toBe(false);
20 });
21
22 it('should reject unknown rank', () => {
23 expect(validator.validateLegionaryRank('Legate')).toBe(false);
24 });
25
26 it('should be case insensitive', () => {
27 expect(validator.validateLegionaryRank('centurion')).toBe(true);
28 expect(validator.validateLegionaryRank('CENTURION')).toBe(true);
29 });
30
31 it('should handle spaces', () => {
32 expect(validator.validateLegionaryRank(' Centurion ')).toBe(true);
33 });
34 });
35});1// Using test.each for different scenarios
2describe('Battle Damage Calculator', () => {
3 // Test all weapon and armor combinations
4 test.each([
5 ['ballista', 'wood', 50, 45],
6 ['ballista', 'iron', 50, 35],
7 ['ballista', 'steel', 50, 25],
8 ['arcus', 'wood', 25, 22],
9 ['arcus', 'iron', 25, 15],
10 ['arcus', 'steel', 25, 8],
11 ['sword', 'wood', 15, 12],
12 ['sword', 'iron', 15, 8],
13 ['sword', 'steel', 15, 4],
14 ])('%s vs %s armor should deal %i base damage, resulting in %i actual damage',
15 (weapon, armor, baseDamage, expectedDamage) => {
16 const result = calculator.calculateDamage(weapon, armor, baseDamage);
17 expect(result).toBe(expectedDamage);
18 }
19 );
20
21 // Test all weather combinations
22 test.each([
23 ['calm', 1.0],
24 ['windy', 0.9],
25 ['stormy', 0.7],
26 ['fog', 0.8],
27 ['rain', 0.85],
28 ])('weather %s should have multiplier %f', (weather, multiplier) => {
29 const result = calculator.getWeatherMultiplier(weather);
30 expect(result).toBeCloseTo(multiplier);
31 });
32});1# .github/workflows/test.yml
2name: Test and Coverage
3
4on: [push, pull_request]
5
6jobs:
7 test:
8 runs-on: ubuntu-latest
9
10 steps:
11 - uses: actions/checkout@v3
12
13 - name: Setup Node.js
14 uses: actions/setup-node@v3
15 with:
16 node-version: '18'
17 cache: 'npm'
18
19 - name: Install dependencies
20 run: npm ci
21
22 - name: Run tests with coverage
23 run: npm run test:cov
24
25 - name: Upload coverage to Codecov
26 uses: codecov/codecov-action@v3
27 with:
28 file: ./coverage/lcov.info
29 flags: unittests
30
31 - name: Coverage Comment
32 uses: romeovs/lcov-reporter-action@v0.3.1
33 with:
34 github-token: ${{ secrets.GITHUB_TOKEN }}
35 lcov-file: ./coverage/lcov.info1// package.json
2{
3 "husky": {
4 "hooks": {
5 "pre-commit": "lint-staged"
6 }
7 },
8 "lint-staged": {
9 "*.{ts,js}": [
10 "eslint --fix",
11 "npm run test:cov -- --passWithNoTests --findRelatedTests"
12 ]
13 }
14}1<!-- README.md -->
2# Legionary Cohort Backend
3
4
5
6
7## Test Coverage Report
8
9| Type | Percentage |
10|------|------------|
11| Statements | 87.5% |
12| Branches | 83.3% |
13| Functions | 90.0% |
14| Lines | 88.0% |1// ❌ Bad example - testing for coverage, not quality
2it('should call all methods for coverage', () => {
3 service.method1(); // Call everything...
4 service.method2(); // ...without checking results
5 service.method3(); // 100% coverage, 0% value!
6
7 expect(true).toBe(true); // Worthless test
8});
9
10// ✅ Good example - meaningful tests
11it('should calculate tribute value correctly', () => {
12 const tribute = { base: 100, rarity: 2.5, condition: 0.8 };
13
14 const result = service.calculateValue(tribute);
15
16 expect(result).toBe(200); // base * rarity * condition
17 expect(result).toBeGreaterThan(tribute.base);
18});1// Good metrics balance:
2// - 80-90% Statement Coverage (general coverage)
3// - 75-85% Branch Coverage (all paths)
4// - 85-95% Function Coverage (all functions)
5// - High test quality (assertion quality)
6
7// Targets for different code types:
8const coverageTargets = {
9 businessLogic: { statements: 90, branches: 85, functions: 95 },
10 controllers: { statements: 80, branches: 75, functions: 90 },
11 utilities: { statements: 95, branches: 90, functions: 100 },
12 configurations: { statements: 70, branches: 60, functions: 80 }
13};Test Coverage is a compass showing which areas of the fort are well explored and which need more attention. Remember - it's not about 100% coverage, but about covering what truly matters for expedition safety!