The Angular CLI includes support for Unit and End-to-End Testing using tools and technologies that are known to work well.

However, if you use Ionic's CLI to create the scaffolding for a new project you'll notice that it doesn't include any support for Unit or End-to-End Testing.

The Angular CLI

In a previous post, I used the Angular CLI to create the scaffolding for a new application:

ng new angular-wijmo-flexsheet

The Angular CLI creates a placeholder package.json file:

"devDependencies": {
  ...
  "jasmine-core": "~2.6.2",
  "jasmine-spec-reporter": "~4.1.0",
  "karma": "~1.7.0",
  "karma-chrome-launcher": "~2.1.1",
  "karma-cli": "~1.0.1",
  "karma-coverage-istanbul-reporter": "^1.2.1",
  "karma-jasmine": "~1.1.0",
  "karma-jasmine-html-reporter": "^0.2.2",
  "protractor": "~5.1.2",
  ...
}

That includes Jasmine, Karma and Protractor as 'devDependencies'.

As well as test and e2e tasks:

"scripts": {
  ...
  "test": "ng test",
  "e2e": "ng e2e",
  ...
},

The Angular CLI also creates a placeholder karma.conf.js file and a placeholder protractor.conf.js file in the project's root directory.

As well as some additional testing-related files in the /src directory:

  • test.ts - Responsible for loading the project's specs.
  • tsconfig.app.json - The Angular CLI's TypeScript config file.
  • tsconfig.spec.json - The Angular CLI's TypeScript spec config file.

Note: Tests written in Jasmine are called specs. The filename extension must be .spec.ts, the convention adhered to by karma.conf.js and other tools.

Looking at a project created using the Angular CLI means we now have a much better idea as to how we might go about adding support for Unit and End-to-End Testing to an existing Ionic project.

What problem are we solving?

As your project evolves unit and end-to-end testing reduces the likelihood of introducing breaking changes when implementing new features and bugfixes.

Unit and End-to-End Testing

If you have an existing Ionic project and you'd like to use the tools and technologies supported by the Angular CLI, you can follow the steps in this post and the associated project.

It's the approach I followed when adding support for unit and end-to-end testing to the Big Top App:

├── /big-top
    └── /config
        ├── karma.conf.js        - Karma's config file
        ├── protractor.conf.js   - Protractor's config file
    └── /coverage
    └── /src
        ├── polyfills.ts         - Pollyfills used by the Angular CLI
        ├── test.ts              - Test driver
        ├── tsconfig.spec.json   - Angular's CLI TypeScript spec config file
    └── /www
    ├── .angular-cli.json        - Angular's CLI config file
    ├── tsconfig.ng-cli.json     - Angular's CLI base compiler config file
    ├── tsconfig.json            - TypeScript's configuration file
    ├── package.json

Note: I've only included the directories and files discussed in this post.

The updated package.json:

"scripts": {
  ...
  "test": "ng test --config ./config/karma.conf.js",
  "test-ci": "ng test --config ./config/karma.conf.js --watch=false --code-coverage",
  "test-coverage": "ng test --config ./config/karma.conf.js --code-coverage",
  "e2e": "npm run e2e-update && npm run e2e-test",
  "e2e-test": "protractor ./config/protractor.conf.js",
  "e2e-update": "webdriver-manager update --standalone false --gecko false",
  ...
}

The updated tsconfig.spec.json:

"compilerOptions": {

  ...
  
  "target": "es5",
  "types": [
    "jasmine",
    "node"
  ],
  "baseUrl": "../src",
  "paths": {
    "@app/*": [ "app/*" ],
    "@assets/*": [ "assets/*" ],
    "@env": [ "environments/environment" ],
    "@pages/*": [ "pages/*" ],
    "@services/*": [ "services/*" ],
    "@tests/*": [ "./*" ],
    "@theme/*": [ "theme/*" ]
  }
},

...

Putting it all together

I started with a simple test to make sure that the setup works as expected:

import { ComponentFixture, async } from '@angular/core/testing';
import { TestUtils } from '@tests/test';
import { SignInPage } from '@pages/sign-in/sign-in';

let fixture: ComponentFixture<SignInPage> = null;
let instance: any = null;

describe('Page: Sign in', () => {

  beforeEach(async(() => TestUtils.beforeEachCompiler([SignInPage]).then(compiled => {
    fixture = compiled.fixture;
    instance = compiled.instance;
    fixture.detectChanges();
  })));

  afterEach(() => {
    fixture.destroy();
  });

  it('Should create the Sign in page', async(() => {
    expect(instance).toBeTruthy();
  }));
});

Let's try running it:

npm test

After a few moments, karma opens a browser and starts writing to the console:

karma

Your console output should look something like:

INFO: 'SignInPage initialised'
Chrome 62.0.3202 (Mac OS X 10.12.6): Executed 0 of 1 SUCCESS (0 secs / 0 secs)
Chrome 62.0.3202 (Mac OS X 10.12.6): Executed 1 of 1 SUCCESS (0 secs / 0.335 secChrome 62.0.3202 (Mac OS X 10.12.6): Executed 1 of 1 SUCCESS (0.344 secs / 0.335 secs)
Source Code:
Unit and End-to-End Testing Resources:
Mobile Security Testing Resources: