Monday, April 17, 2023

Unit Test and Code Coverage Reports with Angular

I recently endeavored to have code coverage available as a metric in Azure DevOps for my Angular application. It turned out to be a little bit (but not much) trickier than I thought it would be so here I am.

I started with this two part article on how to get started, but had to make a few tweaks. You should go read those posts if they're still up, then come back here and skip down to the heading "The Changes".

The Summary

The first thing we have to do is get our test results generated locally. The linked posts above use junit and that met my needs so that's what I'm doing as well.

  1. Install the junit karma reporter: npm install karma-junit-reporter --save-dev
  2. Configure Karma to require junit
    • In karma.conf.js, add require('karma-junit-reporter') to the plugins array (which should already require things like karma-jasmine)
  3. Configure Karma to use junit as a reporter
    • In karma.conf.js, add 'junit' to the reporters array (which should already include 'progress' and 'kjhtml')
  4. Configure the junit reporter within Karma
    • In karma.conf.js, add the following configuration as a sibling of the reporters array in the previous step:
      •         junitReporter: {
          outputDir: 'reports/junit',
          outputFile: 'unit-test-results.xml',
          useBrowserName: false
        }
        		
        		
    • This configuration uses junit to create a file named unit-test-results.xml in a folder named reports/junit as a sibling of the src folder
    • Change the values of outputDir and outputFile to use a different file/folder combination
  5. Make sure Karma is configured to use a headless browser
    • The browsers array accepts ChromeHeadless as a value (it also accepts FirefoxHeadless, but I've had issues with headless Firefox holding on to resources and crashing my system so I tend to avoid it)
  6. We can now run ng test --watch=false to run all of our unit tests once and generate test results

Now that we have test results in an XML file we want to add code coverage results as well. We're going to use karma-coverage to do that part.

  1. Install the karma coverage reporter: npm install karma-coverage --save-dev
  2. Configure Karma to require karma coverage
    • In karma.conf.js, add require('karma-coverage') to the plugins array (the same array from step 2 in the previous section)
  3. Configure Karma to use karma coverage as a reporter
    • In karma.conf.js, add 'coverage' to the reporters array
  4. Configure the karma coverage reporter the way you want it
    • When you use the Angular CLI, karma-coverage is actually configured by default, but you may want to change the values
    • You can see the default configuration by looking for the coverageReporter node that is a sibling of the reporters array
    • I changed mine to output the file in the same reporters folder as the unit test results (reports/), in a subfolder called coverage
      • dir: require('path').join(__dirname, './reports/coverage'),
    • I also removed the text-summary reporter configuration and added the cobertura reporter configuration, specifying the file name to be code-coverage.xml
      • coverageReporter: {
          dir: require('path').join(__dirname, './reports/coverage'),
          subdir: '.',
          reporters: [
            { type: 'html' },
            {
              type: 'cobertura',
              file: 'code-coverage.xml'
            }
          ]
        }
        
        
  5. We can now run ng test --watch=false --code-coverage to run all of our unit tests once and generate test results and code coverage results
  6. The last thing we need to do is exclude files from code coverage that we don't want to... well, include
    • In angular.json, locate the projects/<project name>/architect/test node and add a codeCoverageExclude property, which is an array of strings
      • In the array, specify each fully qualified path - starting with src - to exclude from code coverage

That's it! Just like that, we have unit tests and code coverage results, but now we want to display those results in Azure DevOps. It turns out that's pretty easy, too.

  1. In your package.json, create a script to run your tests without watching and with code coverage results generated, to make it easier to do this from your build
    • "test-code-coverage": "ng test --watch=false --code-coverage"
  2. Create a new build (or add a test step to your current build, but I prefer to run my unit tests when a pull request is created rather than while I'm trying to deploy something)
    • pool:
        name: Azure Pipelines
      steps:
        - task: Npm@1
          displayName: 'npm install'
          inputs:
            verbose: false
        
        - task: Npm@1
          displayName: 'Execute tests'
          input:
            command: custom
            verbose: false
            customCommand: 'run test-code-coverage'
            
        - task: PublishTestResults@1
          displayName: 'Publish Test Results - Generate Test Report'
          inputs:
            testResultsFiles: '**/reports/junit/unit-test-results.xml'
            
        - task: PublishCodeCoverageResults@1
          displayName: 'Unit Test Code Coverage Report'
          inputs:
            codeCoverageTool: Cobertura
            summaryFileLocation: '**/reports/coverage/code-coverage.xml'
      
  3. As long as you publish your test results to the pipeline (as in the sample YAML above), Azure DevOps will automatically pick up the results and display them in a nice little UI for you