Hello Everyone 👋 hope you are doing good.
This blog post is a continuation of “Angular testing with Cypress” series,
- Introduction to Cypress with Angular
- 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
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,
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
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.
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.
7. And… Voila 🥳🥳🥳!! When we run tests, everything passes and we see all asserts were successful.
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,
6. Let’s push these changes and see if our CI is setup properly.
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 😎🤘