Unit testing in Angular with Cypress

Hello Everyone 👋 hope you are doing good.

This blog post is a continuation of “Angular testing with Cypress” series,

  1. Introduction to Cypress with Angular
  2. Unit tests using Cypress & Angular (👈 we are here)

Prerequisite

Just glance over my last blog post Introduction to Cypress with Angular to get a basic idea about what is Cypress and how it works with Angular.

Agenda

  1. What is unit testing?
  2. Setup
  3. Writing first unit test
  4. Testing a reusable function
  5. Setting up CI
  6. Conclusion

What is unit testing? #

To put it in simple words, Unit Testing is a type of software testing where we test basic building blocks(smallest testable units/components) of software.

It is usually done during coding phase and helps detect smaller bugs introduced while modifying certain unit of software. It also prevents from bubbling up smaller bugs into complex defects and hence gives more confidence to developer.

Setup #

1. Run following command in your existing project,

npm install -D cypress cypress-angular-unit-test angular2-template-loader to-string-loader css-loader sass-loader html-loader

2. We especially need following plugin by bahumtov to run our unit tests 👉 cypress-angular-unit-test.

3. All other loader libraries are necessary for webpack to parse different types of files in our project. You can find more about loaders here.

4. Add following lines to your cypress > support > index.ts file

// cypress/support/index.ts
// core-js 3.*
require('core-js/es/reflect'); // 👈 I'm keeping only this import statement
// core-js 2.*
require('core-js/es7/reflect');
require('cypress-angular-unit-test/support');

5. Add following properties to cypress.json file,

{
  "experimentalComponentTesting": true,
  "componentFolder": "src",
  "testFiles": "**/*cy-spec.*"
}

Writing first unit test #

1. Change your cypress > tsconfig.json file to look like this,

{
  "extends": "../tsconfig.json",
  "include": [
    "../node_modules/cypress",
    "*/*.ts"
  ],
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "skipLibCheck": true
  }
}

2. Also change your cypress > plugins > cy-ts-preprocessor.ts file to look like this,

const webpackPreprocessor = require("@cypress/webpack-preprocessor");

const webpackOptions = {
  resolve: {
    extensions: [".ts", ".js"]
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: [/node_modules/],
        use: [
          {
            loader: "ts-loader",
            options: {
              transpileOnly: true
            }
          }, {
            loader: 'angular2-template-loader'
          }
        ],
      },
      {
        test: /\.(scss)$/,
        loaders: ['to-string-loader', 'css-loader', 'sass-loader'],
        exclude: /\.async\.(scss)$/
      },
      {
        test: /\.(html)$/,
        loader: 'html-loader',
        exclude: /\.async\.(css)$/
      },
      {
        test: /\.async\.(html|scss)$/,
        loaders: ['file?name=[name].[hash].[ext]', 'extract']
      }
    ]
  }
};

const options = {
  webpackOptions
};

module.exports = webpackPreprocessor(options);

3. Let’s create a spec file to write some unit test which checks app component,

creating app.component.cy-spec.ts file

4. Add following code to the file app.component.cy-spec.ts

/// <reference types="cypress" />

import { initEnv, mount } from 'cypress-angular-unit-test';
import { AppComponent } from './app.component';
// This 👇 is a dependency needed for router-outlet located in our app.component.html
import { RouterTestingModule } from '@angular/router/testing';

beforeEach(() => {
  // Init Angular stuff
  initEnv(AppComponent, { imports: [ RouterTestingModule ] });
});

describe('AppComponent', () => {
  it('Test welcome message', () => {
		const welcomeMessage = 'blog-angular-cypress-example';
    // component + any inputs object
    mount(AppComponent, {title: welcomeMessage});
    // use any Cypress command afterwards
    cy.contains(`${welcomeMessage} app is running!`);
  });
});

5. Make sure you have following scripts configured in package.json,

"cy:open": "cypress open",
"cy:run": "cypress run"

6. Now let’s open Cypress Test Runner UI using command,

npm run cy:open

7. You will see all the tests in your application in a neatly organized manner like below

Unit tests shown as Component test

8. Now choose browser of your liking and click on a test to run it.

9. Hurray 🥳🥳🥳… We have successfully executed our first unit test written using cypress.

Running unit test for first time

Testing a reusable function #

1. Let’s write a small function that adds two numbers and try implement a unit test for it. Reusable functions like these are another great candidates for unit testing as a lot of other parts of your application rely on them.

2. Start by creating a file app.service.ts which will export this function. Name this file anything you want as per your project conventions for helper files.

3. Add following content to the file,

/**
 * Adds two numbers and returns the result
 * @param number1 first number
 * @param number2 second number
 */
export const addNumbers = (number1: number, number2: number): number => {
  return number1 + number2;
};

4. Go back to our app.component.cy-test.ts and after adding test it should look like this,

/// <reference types="cypress" />

import { initEnv, mount } from 'cypress-angular-unit-test';
import { AppComponent } from './app.component';
// This 👇 is a dependency needed for router-outlet located in our app.component.html
import { RouterTestingModule } from '@angular/router/testing';
// This 👇 is for testing addNumbers function
import { addNumbers } from './app.service';

beforeEach(() => {
  // Init Angular stuff
  initEnv(AppComponent, { imports: [ RouterTestingModule ] });
});

describe('AppComponent', () => {
  it('Test welcome message', () => {
    const welcomeMessage = 'blog-angular-cypress-example';
    // component + any inputs object
    mount(AppComponent, {title: welcomeMessage});
    // use any Cypress command afterwards
    cy.contains(`${welcomeMessage} app is running!`);
  });

  it('Test addNumbers() function', () => {
    // Validate if it is a function
    expect(addNumbers, 'addNumbers()').to.be.a('function');
    // Test the function
    const number1 = 10;
    const number2 = 20;
    const correctAddition = 30;
    expect(addNumbers(number1, number2), `addNumbers(${number1}, ${number2})`).to.eq(correctAddition);
    // Make sure it returns value of type number
    expect(addNumbers(number1, number2)).to.be.a('number');
  });
});

5. As you can see, we are testing 3 things here. First we validate whether it is a function. Then we check if it returns correct addition and finally check if it returns value of number type.

6. To assert all these aspects we are using Chai assertions which comes pre-configured with cypress. You can find Awesome Intellisense Support in VS Code like below.

Awesome Intellisense support in VS Code

7. And… Voila 🥳🥳🥳!! When we run tests, everything passes and we see all asserts were successful.

Testing reusable function with assertions

Setting up CI #

1. In our last post, along with introducing Cypress we also setup small CI workflow to run e2e tests using Github Actions.

2. You can find more about it here.

3. Same script is going to be enough to run our unit tests as well. So let’s change the script name from e2e:ci to test:ci

4. Also make the same changes to script in workflow file like below,

# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Cypress Tests

on:
  push:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [10.x, 12.x, 14.x]

    steps:
    - uses: actions/checkout@v2

    # Sets up multiple Node.js environment specified as per array in matrix above
    # This will result in running same workflow for different environments.
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}

    - name: Install node-modules
      run: npm ci

    - name: Run tests
      run: npm run test:ci

5. Let’s run it locally just to double check whether it works. And yes it does work,

Running tests successfully locally

6. Let’s push these changes and see if our CI is setup properly.

CI successfully configured

7. Yay… Our CI works as well 🚀🚀

Conclusion #

Today, we learned quite a lot of things about Unit testing in Angular with Cypress 💪💪.

As you can tell, experience working with Cypress is pretty reassuring and futuristic as compared to other testing frameworks for web applications.

In next part of this series, we will learn about fixtures, dynamic tests and mocking http calls 🚀🚀.


To explore more examples using cypress-angular-unit-test, click here.

To learn more about cypress, visit cypress website.

For reference code, visit my Github repository here.


Hope you liked this post, feel free to share if you find it helpful 😊.

For more such content, click here.

Till next time, Take care and keep learning 😎🤘

Spread the knowledge
Indrajeet Nikam
Indrajeet Nikam

I am tech enthusiast with love for web development ❤

Articles: 15