Files
Dawson Blackhouse 91c53144a1 feat: initial commit
2026-01-10 23:40:52 -05:00

7.4 KiB

Code Coverage Analysis Guide

Understanding Code Coverage

Code coverage measures what percentage of your code is executed by your tests. It's an important metric for test quality.

Coverage Types

Type Measures Target
Line Coverage Percentage of lines executed 80%+
Branch Coverage Percentage of conditional branches executed 75%+
Function Coverage Percentage of functions called 80%+
Statement Coverage Percentage of statements executed 80%+

Using Istanbul/NYC for Coverage

Installation

npm install --save-dev istanbul nyc

Configuration

In package.json:

{
  "scripts": {
    "test": "karma start",
    "test:coverage": "karma start --coverage"
  }
}

In karma.conf.js:

coverageReporter: {
  dir: require('path').join(__dirname, 'coverage'),
  subdir: '.',
  reporters: [
    { type: 'html' },
    { type: 'text-summary' },
    { type: 'lcov' }
  ],
  check: {
    global: {
      statements: 80,
      branches: 75,
      functions: 80,
      lines: 80
    }
  }
}

Running Coverage

npm run test:coverage

Interpreting Coverage Reports

HTML Report

The coverage report generates an index.html file:

coverage/
├── index.html          (Main report)
├── base/
│   ├── src/
│   │   ├── services/
│   │   │   ├── UserService.js  (Coverage details)
│   │   │   └── user.service.html
│   │   └── controllers/
│   │       ├── UserController.js
│   │       └── user.controller.html

Reading Coverage Numbers

Statements   : 85.5% ( 320/375 )    ← 320 of 375 statements executed
Branches     : 78.2% ( 86/110 )     ← 78% of conditional branches covered
Functions    : 82.1% ( 32/39 )      ← 82% of functions called
Lines        : 86.3% ( 298/345 )    ← 86% of lines executed

Visual Indicators

// ✓ Line executed (green)
if (user) {
  console.log(user.name);
}

// ✗ Line not executed (red)
if (admin) {
  deleteAllData(); // This code path not tested
}

// ~ Branch not fully covered (yellow)
if (user && user.active) {  // Some branches missing
  doSomething();
}

Improving Coverage

Finding Coverage Gaps

  1. Open coverage/index.html in browser
  2. Look for red (uncovered) lines
  3. Check yellow (partially covered) branches
  4. Identify missing test scenarios

Common Coverage Gaps

Error Handling

// BAD: No error handling test
service.getUser = function(id) {
  return $http.get('/api/users/' + id);
};

// GOOD: Test error path
it('should handle 404 errors', function() {
  $httpBackend.expectGET('/api/users/999').respond(404);
  
  var error;
  service.getUser(999).then(null, function(err) {
    error = err;
  });
  
  $httpBackend.flush();
  expect(error).toBeDefined();
});

Conditional Branches

// BAD: Only testing happy path
controller.submit = function() {
  if (form.$valid) {
    service.save(form.data);
  }
};

it('should save when form is valid', function() {
  $scope.form = { $valid: true, data: {} };
  $scope.submit();
  // Only tests valid branch!
});

// GOOD: Test both branches
it('should not save when form is invalid', function() {
  $scope.form = { $valid: false };
  $scope.submit();
  expect(service.save).not.toHaveBeenCalled();
});

Edge Cases

// BAD: Not testing edge cases
function processUsers(users) {
  return users.map(function(user) {
    return user.name.toUpperCase();
  });
}

// GOOD: Test edge cases
it('should handle empty array', function() {
  expect(processUsers([])).toEqual([]);
});

it('should handle null users', function() {
  expect(function() {
    processUsers([null]);
  }).toThrow();
});

it('should handle users without names', function() {
  expect(function() {
    processUsers([{}]);
  }).toThrow();
});

Loop Coverage

// BAD: Incomplete loop testing
for (var i = 0; i < items.length; i++) {
  if (items[i].active) {
    results.push(items[i]);
  }
}

// GOOD: Test different loop scenarios
it('should process all items', function() {
  var items = [
    { active: true },
    { active: false },
    { active: true }
  ];
  
  expect(processItems(items).length).toBe(2);
});

Coverage-Driven Testing

Workflow

  1. Run tests with coverage
  2. Identify uncovered code
  3. Write tests for uncovered paths
  4. Re-run coverage
  5. Repeat until target reached

Example

// 1. Run coverage
npm run test:coverage
// Coverage: 65%

// 2. Add missing tests for error handling
it('should handle timeouts', function() {
  $timeout(function() {
    expect(true).toBe(true);
  }, 5000);
  $timeout.flush();
});

// 3. Re-run coverage
npm run test:coverage
// Coverage: 78%

// 4. Continue improving

Coverage Best Practices

✓ Do ✗ Don't
Test error paths Only test happy paths
Test edge cases Test only main scenarios
Test boundary values Ignore edge cases
Test false conditions Only test true conditions
Test async operations Skip async tests
Track coverage trends Ignore coverage metrics

Coverage Reporting

Text Summary

TOTAL: 85.5%
PASS: ✓ 150 tests passed
WARNINGS: 5 files below threshold

CI/CD Integration

GitHub Actions

- name: Test Coverage
  run: npm run test:coverage

- name: Upload Coverage
  uses: codecov/codecov-action@v2
  with:
    files: ./coverage/lcov.info

Jenkins

stage('Coverage') {
  steps {
    sh 'npm run test:coverage'
    publishHTML([
      reportDir: 'coverage',
      reportFiles: 'index.html',
      reportName: 'Coverage Report'
    ])
  }
}

Managing Coverage Thresholds

Setting Thresholds

// karma.conf.js
coverageReporter: {
  check: {
    global: {
      statements: 80,
      branches: 75,
      functions: 80,
      lines: 80
    },
    each: {
      statements: 70,
      branches: 65,
      functions: 70,
      lines: 70
    }
  }
}

Excluding Code from Coverage

// Skip coverage for this file
// istanbul ignore file

// Skip single line
/* istanbul ignore next */ 
if (process.env.NODE_ENV === 'test') { ... }

// Skip next line
/* istanbul ignore next */
console.log('Debug info');

Common Coverage Challenges

Challenge 1: Circular Dependencies

// Mock circular deps in tests
beforeEach(module(function($provide) {
  $provide.value('ServiceA', mockServiceA);
  $provide.value('ServiceB', mockServiceB);
}));

Challenge 2: Browser APIs

// Mock browser APIs
beforeEach(function() {
  spyOn(localStorage, 'getItem').and.returnValue('value');
  spyOn(window, 'alert');
});

Challenge 3: Heavy Setup Code

// Extract setup to helper functions
function setupDatabase() {
  // Complex setup
}

function setupAuth() {
  // Auth setup
}

beforeEach(function() {
  setupDatabase();
  setupAuth();
});

Coverage Tools Comparison

Tool Language Integration Report
Istanbul JavaScript Karma HTML, LCOV
NYC JavaScript CLI HTML, JSON
Coveralls Multi CI/CD Web
Codecov Multi CI/CD Web

References


Last Updated: January 10, 2026