Angular DevOps Series (PART 3)

Are you searching for a solution for automating the Angular application development process? In this series, I’m taking you along with me in my journey towards a fully automated process for my Angular application. The series includes 4 parts:

Part 1: Establish Workflows from Source Code to Staging Environment Using GitHub, CircleCI, Docker and Firebase.

Part 2: First Quality Gate: Static Testing.

Part 3: Second Quality Gate: Unit Testing.

Part 4: Third Quality Gate: E2E Testing.

Let’s go!


Angular DevOps – Part 3: Second Quality Gate – Unit Testing

Overview

We have now come to Part 3 in the series Angular DevOps, where we will begin building the next quality gate Unit Testing. In this section, we will cover:

  • How to implement and integrate the Unit Testing gate to Auto Test pipeline

Auto Test Pipeline

The Auto Test aims to automatically run the appropriate tests for an application and provide feedback at every checkpoint. Unit Testing is needed on developed features. End-To-End Testing is needed on the compiled source code. Regression Testing and more are needed on the Application Under Test (AUT) to prepare for production.

The main goal of the Auto Test pipeline in part 3 is to trigger Unit Testing when a new pull request is created or updated.

Unit Testing

Throughout this article, I’m going to make some unit test cases to test a simple component. After that, these test scripts will be executed in the Unit Testing gate of the Auto Test pipeline. To learn more about Angular Testing, refer to the guideline, Angular Testing.

Let’s go!

Below is about.component.spec.ts file, which is used to implement unit testing for About component:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { AboutComponent } from './about.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';

describe('AboutComponent', () => {
  let component: AboutComponent;
  let fixture: ComponentFixture<AboutComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [AboutComponent],
      schemas: [NO_ERRORS_SCHEMA]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(AboutComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should have About <h2>', () => {
    const h2: HTMLElement = fixture.nativeElement.querySelector('h2');
    const header = h2.textContent;
    expect(header).toBe('About');
  });

  it('should have Zero To Hero - Angular DevOps <h3>', () => {
    const h3: HTMLElement = fixture.nativeElement.querySelector('h3');
    const header = h3.textContent;
    expect(header).toBe('Zero To Hero - Angular DevOps');
  });
});

Figure 1: about.component.spec.ts file (A unit test specification file to test the About component)

Angular framework integrates Karma as the test runner and Jasmine as the test framework to implement unit testing. You are able to find the Karma configuration file karma.conf.js in the root project folder, where you can find easy to integrate plugins, specify test browser, log level, reports, etc.

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, '../coverage'),
      reports: ['html', 'lcovonly'],
      fixWebpackSourcePaths: true
    },
    angularCli: {
      environment: 'dev'
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

Figure 2: karma.conf.js file (A configuration file for Karma test runner)

We also always have a test.ts file to specify test files for running.

import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';

declare const require: any;

getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());

const context = require.context('./', true, /\.spec\.ts$/);

context.keys().map(context);

Figure 3: test.ts file (A configuration file for ng test command to initialize test environment and filter test specification files)

Now, we’ll execute the command ng test to run unit test scripts. With default Karma configurations, Karma runner initializes and opens a Chrome browser to run unit test scripts. A test report will be generated as screenshot below for test results.

Figure 4: Karma test report (A Karma test report with HTML format)

However, to run unit test scripts in a CI system, such as CircleCI, we need to implement some changes:

  • Using ChromeHeadless instead of Chrome browser: Headless browsers are typically faster than real browsers; additionally, you are able to do all of this from a command line without having to manually refresh or start a browser, saving you lots of time and effort.
  • Using Allure HTML Report instead of the Karma report: With an HTML report, you are able to save report files to the CircleCI build artifact for download and review directly.

Let’s open the Karma configuration file karma.conf.js and update the browsers and singleRun options.

browsers: ['ChromeHeadless'],
singleRun: true

To use generated Allure test results after executing unit test scripts, you’ll need to install the package, karma-allure-reporter, and follow the guideline to configure the Allure report.

Below is the karma.conf.js file with the Allure report and ChromeHeadless configuration.

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma'),
      require('karma-allure-reporter'),
    ],
    client: {
      clearContext: false
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, '../coverage'),
      reports: ['html', 'lcovonly'],
      fixWebpackSourcePaths: true
    },
    angularCli: {
      environment: 'dev'
    },
    reporters: ['progress', 'allure'],
    allureReport: {
      reportDir: 'allure-result',
      useBrowserName: false,
    },
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['ChromeHeadless'],
    singleRun: true
  });
};

Figure 5: karma.conf.js file (Integrate the Allure report plugin to the Karma configuration file)

You have to install the package allure-commandline to use the allure command in next steps.

sudo npm install -g allure-commandline

Now, generate an Allure HTML Report from the Allure test results, which are stored in src/allure-result, and then start a local web server.

allure serve src/allure-result

Figure 6: Allure test report (An Allure test report with HTML format)

Unit Testing Gate in CircleCI

We used the CircleCI to setup CI workflows and the Static Testing quality gate in previous parts of this Angular DevOps series. In this section, we will continue to establish the Unit Testing quality gate into CI workflows. You can follow the same steps that you would if you were manually executing unit test scripts and generating an Allure HTML report; the steps are:

  1. Install the package allure-commandline on CircleCI container to support allure commands.
  2. Execute unit test scripts with the command npm run test
  3. Generate an Allure HTML report from Allure test results, and save the report as a CircleCI artifact.

Now, open the config.yml file and define a unit-testing job.

unit-testing:
    executor: my-executor
    parameters:
      target-path:
        description: Path for report directory
        type: string
        default: allure-report
      results-path:
        description: Path to directory with test results
        type: string
        default: src/allure-result
      artifact-path:
        description: Path that will be used when storing result as artifact
        type: string
        default: Report/Allure
    steps:
      - attach_workspace:
          at: ~/angular-herofromzero
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
            - v1-dependencies-
      - run:
          name: Install Allure
          command: sudo npm install -g allure-commandline --save-dev
      - run:
          name: Testing
          command: npm run test
      - run:
          name: Allure report generation (<< parameters.results-path >> -> << parameters.target-path >>)
          command: |
            allure generate \
              --report-dir << parameters.target-path >> \
              << parameters.results-path >>
          when: always
      - store_artifacts:
          path: << parameters.target-path >>
          destination: << parameters.artifact-path >>

Figure 7: config.yml file (CircleCI configuration file with the unit-testing job)

Integrate the Unit Testing Gate to CI Workflows

After the Static Testing gate, source code then has to go through the Unit Testing gate in order to ensure that each piece of the software performs as designed before going to the Auto Build and Auto Deploy pipelines. If there are any code problems, the Unit Testing gate will prevent source code from entering the workflow and notify the developer of these code problems.

To set this up, let’s open the config.yml file and edit the section workflows. We need to let the CircleCI know about the unit-testing job and require this job in build job.

workflows:
  version: 2
  nightly:
    triggers:
      - schedule:
          cron: '0 0 * * *'
          filters:
            branches:
              only:
                - master
    jobs:
      - build
  commit:
    jobs:
      - setup-environment
      - static-testing:
          requires:
            - setup-environment
      - unit-testing:
          requires:
            - setup-environment
      - build:
          requires:
            - setup-environment
            - static-testing
            - unit-testing
      - build-docker:
          requires:
            - build
          filters:
            branches:
              only:
                - /staging-.*/
                - master
      - deploy:
          requires:
            - build
          filters:
            branches:
              only:
                - master

Figure 8: config.yml file (Update CircleCI workflows to require the unit-testing job)

Integrate the Unit Testing Gate to Git Branches.

We are going to require the Unit Testing gate for Git pull requests. That combined with the Static Testing gate will enforce a quality policy for the source code. You need to update Rule settings for specified branches.

Figure 9: GitHub Branches Settings (Requires the unit-testing check for a pull request to development branches)


Verify Auto Test pipeline

To trigger the Auto Test pipeline and verify the Unit Testing quality gate, we need to either create a new pull request or update a previous one. I will continue to work with the pull request of branch zth-002, which was mentioned in Angular DevOps Part 2.

Let’s open the about.component.ts file to edit the template of About component.

I put About title in h1 tag while the unit test case should have About <h2>; expect that the About title is put in h2 tag.

import { Component } from '@angular/core';
@Component({
  template: `
    <h1>About</h1>
    <h3>Zero To Hero - Angular DevOps</h3>
  `,
})
export class AboutComponent { }

Figure 10: about.component.ts file (An implementation file for the About component)

I made a new commit for these changes and expect that the Unit Testing quality gate will be triggered as designed.

As per our design, the Auto Test pipeline is triggered to immediately run unit test scripts and requires:

  • At least 2 approvals.
  • Check points setup-environmentbuildstatic-testing and unit-testing need be PASSED.

Figure 11: All required checks are triggered (All required checks are triggered to check a new pull request)

The Unit Testing gate prevented bad source code, which could have adversely affected the login feature, and updated its status to “FAILED” on the pull request. You can see this in the screenshot below.

Figure 12: unit-testing check is failed (The unit-testing check updates a FAILED status on a pull request)

You can click on the Details link to view CI build details and the Unit Testing report.

Figure 13: unit-testing CircleCI build (A unit-testing CircleCI build with FAILED result)

Figure 14: Allure test report on a CircleCI build (An Allure test report is saved as a CircleCI build artifact)

From here, you are able to fix detected errors and update the pull request again. The pull request is able to be merged with quality points that are “PASSED” with at least 2 approvals.

Figure 15: All required checks are PASSED (A pull request is ready to merge with all required checks are PASSED)

Figure 16: unit-testing CircleCI build (A unit-testing CircleCI build with PASSED result)


Summary

We’ve come a long way with Angular DevOps – Part 3: Second Quality Gate – Unit Testing; the Unit Testing gate is established and integrated to the Auto Test pipeline! So far, we have used 2 quality gates—Static Testing and Unit Testing—to ensure source code quality before it is sent to the Auto Build and Auto Deploy pipelines.

Now, let’s take a look at the CI workflow chart.

Figure 17: CI workflows chart (The CI workflows chart with the Unit Testing quality gate is integrated)

Next up, we are going to add the quality gate End-To-End Testing to ensure that integrated components of an application are functioning as expected. Stay tuned for part 4!

Luong Mai
Luong Mai joined LogiGear in 2012 as an Automation Engineer. Currently, he works as a Software Developer for MOWEDE. He is responsible for developing front end applications (mobile, web) and DevOps in web development. Mai loves to automate everything in software development in order to maximize the productivity of development teams and is actively involved in developing software framework in MOWEDE
Luong Mai
Luong Mai joined LogiGear in 2012 as an Automation Engineer. Currently, he works as a Software Developer for MOWEDE. He is responsible for developing front end applications (mobile, web) and DevOps in web development. Mai loves to automate everything in software development in order to maximize the productivity of development teams and is actively involved in developing software framework in MOWEDE