Testing in Ionic 2

Introduction to Testing Ionic 2 Applications with TestBed



·

Creating automated tests for your application is a great way to improve the quality of your code, protect against code breaking regressions when implementing new features, and speed up testing and debugging.

In a Javascript environment, the Jasmine framework is typically used for writing tests and Karma is used to run them. This is still the case for testing Ionic 2 applications, but Angular 2 has introduced the concept of the TestBed. Since Ionic 2 applications are built on top of Angular 2, we can also make use of TestBed in our Ionic 2 applications.

The general concept is that TestBed allows you to set up an independent module, just like the @NgModule that lives in the app.module.ts file, for testing a specific component. This is an isolated testing environment for the component you are testing. A unit test focuses on testing one chunk of code in isolation, not how it integrates with any other parts of the code (there are different tests for this), so it makes sense to create an environment where we get rid of any outside influences.

Before we go any further, here’s a look at what an implementation using TestBed looks like:

import { TestBed, ComponentFixture, async, inject } from '@angular/core/testing';
import { IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

let comp: MyApp;
let fixture: ComponentFixture<MyApp>;

describe('Component: Root Component', () => {

	beforeEach(async(() => {

		TestBed.configureTestingModule({

			declarations: [MyApp],
			
			providers: [

			],

			imports: [
		        IonicModule.forRoot(MyApp)
			]

		}).compileComponents();

	}));

	beforeEach(() => {

		fixture = TestBed.createComponent(MyApp);
		comp 	= fixture.componentInstance;

	});

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

	it('is created', () => {

		expect(fixture).toBeTruthy();
		expect(comp).toBeTruthy();

	});

	it('initialises with a root page of HomePage', () => {
		expect(comp['rootPage']).toBe(HomePage);
	});

});

We are going to walk through building this later, but I want you to have some idea of what using TestBed looks like right away.

You don’t always need to use TestBed when creating unit tests, you can test your providers and services without it – in fact, that is the recommended way. If you want more of an introduction to Jasmine and Karma themselves (which we won’t be covering in this blog post), you should take a look at How to Unit Test an Ionic 2 Application which also covers how to create a simple test for a service without TestBed.

NOTE: If you want to run the tests yourself, I would advise following the setup guide for Jasmine and Karma in this post as it will be the guide I will be focusing on keeping up to date.

I’m going to quickly cover some important points to understand when working with TestBed, but this guide goes into a lot more detail. I would highly recommend reading through at least the beginning sections when you have time.

  • The beforeEach sections will run before each of the tests are executed (each it block is a test). The afterEach will run after the test has completed.
  • We can use configureTestingModule to set up the testing environment for the component, including any imports and providers that are required.
  • We can use createComponent to create an instance of the component we want to test after configuring it
  • We need to use compileComponents when we need to asynchronously compile a component, such as one that has an external template (one that is loaded through templateUrl and isn’t inlined with template). This is why the beforeEach block that this code runs in uses an async parameter – it sets up an asynchronous test zone for the compileComponents to run inside.
  • In order to wait for compileComponents to finish (so we can set up references to the component we created), we can either chain a then() onto it and then call createComponent from inside of there, or we can just create a second beforeEach which will wait until the component has finished compiling before running.
  • A Component Fixture is a handle on the testing environment that was created for the component, compared to the instance which is a reference to the component itself (and is what we use for testing).
  • Change detection, like when you change a variable with two-way data binding set up, is not automatic when using TestBed. You need to manually call fixture.detectChanges() when testing changes. If you would prefer, there is a way to set up automatic change detection for tests.

You don’t need to understand all of the above concepts right away, I just want them to be in your mind as we walk through some examples.

In this tutorial, we are going to set up a testing environment that allows us to make use of TestBed when testing an Ionic 2 application. We will also set up some simple, real-world scenario, tests to test our root component and the home page of the application.

Before We Get Started

Before you go through this tutorial, you should have at least a basic understanding of Ionic 2 concepts and the differences to Ionic 1. You must also already have Ionic 2 installed on your machine.

If you’re not familiar with Ionic 2 already, I’d recommend reading my Ionic 2 Beginners Guide first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic 2, then take a look at Building Mobile Apps with Ionic 2.

1. Generate a New Ionic 2 Application

We are going to generate a new blank Ionic 2 application to test. Our testing is going to be very simple in this example, so we won’t need to generate any additional pages or providers.

Run the following command to generate a new Ionic 2 application:

ionic start ionic2-testbed blank --v2

Once the application has finished generating, make it your working directory by running the following command:

cd ionic2-testbed

2. Set up Testing

IMPORTANT: I would recommend not following the following setup steps outlined below. I will keep the manual setup steps below for people who are interested, but it is much easier to just clone this respository which is an example testing application maintained by the community that has everything set up that you need. To get up and running, all you need to do is create a new project by running:

git clone https://github.com/ionic-team/ionic-unit-testing-example.git my-project-name

and then change into that new project and run:

npm install

this will set up all of the dependencies you need for testing. I also have another testing repository based on this one set up here. This was created for my Elite Ionic course and has less boilerplate included than the example testing repository. If you want to start from more of a clean slate, you can instead run:

git clone https://github.com/joshuamorony/ionic-testing-elite-ionic.git my-project-name

and then:

npm install

to create a new project with tests already set up that doesn’t include example pages/tests.

OUTDATED: You can follow the manual setup steps below if you wish, but I would recommend the method above instead. If you already have tests set up, you can skip right to the 3. Create a Test for the Root Component section in this post.

There are quite a few steps involved to get the appropriate testing environment set up for TestBed in your Ionic application, so we will try and get through this as quickly as possible. Much of this is based on the Clicker repository by Stephen Hazleton, however, I have simplified it quite a bit as the testing set up for that project is quite a bit more advanced.

In the future, Ionic have plans to integrate testing directly into the Ionic environment, which should make the testing set up a lot easier. Until then we will just have to make do.

2.1 Install Dependencies

Get your command line ready, we’re going to install a bunch of dependencies with npm now. Just run all of the following commands.

npm install -g karma-cli
npm install angular-cli --save-dev
npm install codecov --save-dev
npm install jasmine-core --save-dev
npm install jasmine-spec-reporter --save-dev
npm install karma --save-dev
npm install karma-chrome-launcher --save-dev
npm install karma-jasmine --save-dev
npm install karma-mocha-reporter --save-dev
npm install karma-remap-istanbul --save-dev
npm install ts-node --save-dev
npm install tslint --save-dev
npm install tslint-eslint-rules --save-dev
npm install @types/jasmine --save-dev
npm install @types/node --save-dev

2.2 Set up Karma

We are going to generate a Karma configuration file by using the Karma CLI, but we are just going to keep all the defaults for now because we are going to overwrite the configuration file manually anyway.

Run the following command and accept all of the default options:

karma init karma.conf.js

Modify karma.conf.js to reflect the following:

// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', 'angular-cli'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-remap-istanbul'),
      require('karma-mocha-reporter'),
      require('angular-cli/plugins/karma')
    ],
    files: [
      { pattern: './src/test.ts', watched: false }
    ],
    preprocessors: {
      './src/test.ts': ['angular-cli']
    },
    mime: {
      'text/x-typescript': ['ts','tsx']
    },
    remapIstanbulReporter: {
      reports: {
        html: 'coverage',
        lcovonly: './coverage/coverage.lcov'
      }
    },
    angularCli: {
      config: './angular-cli.json',
      environment: 'dev'
    },
    reporters: config.angularCli && config.angularCli.codeCoverage
              ? ['mocha', 'karma-remap-istanbul']
              : ['mocha'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
};

2.3 Configure Angular CLI and Setup Testing Environments

Create a file in the root folder of your project called angular-cli.json and add the following:

{
  "project": {
    "version": "1.0.0-beta.22-1",
    "name": "ionic2-tdd"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets"
      ],
      "index": "index.html",
      "main": "main.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.test.json",
      "prefix": "app",
      "mobile": false,
      "styles": [
        "styles.css"
      ],
      "scripts": [],
      "environments": {
        "source": "environments/environment.ts",
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  "addons": [],
  "packages": [],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "prefixInterfaces": false,
    "inline": {
      "style": false,
      "template": false
    },
    "spec": {
      "class": false,
      "component": true,
      "directive": true,
      "module": false,
      "pipe": true,
      "service": true
    }
  }
}

Create a folder inside of src called environments

Create a file at src/environments/environment.prod.ts and add the following:

export const environment: any = {
  production: true,
};

Create a file at src/environment/environment.ts and add the following:

// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `angular-cli.json`.

export const environment: any = {
  production: false,
};

2.4 Set up Mocks and Polyfills

The mocks file we are about to create supplies us with simplified versions of real classes. We call them "mocks" because they are pretending to be something else. Often when testing we don't want to rely on a lot of external complicated code, we just want to focus on what we are testing, so we can use simple implementations of those classes as a substitute.

Create a file at src/mocks.ts and add the following:

export class ConfigMock {

  public get(): any {
    return '';
  }

  public getBoolean(): boolean {
    return true;
  }

  public getNumber(): number {
    return 1;
  }
}

export class FormMock {
  public register(): any {
    return true;
  }
}

export class NavMock {

  public pop(): any {
    return new Promise(function(resolve: Function): void {
      resolve();
    });
  }

  public push(): any {
    return new Promise(function(resolve: Function): void {
      resolve();
    });
  }

  public getActive(): any {
    return {
      'instance': {
        'model': 'something',
      },
    };
  }

  public setRoot(): any {
    return true;
  }
}

export class PlatformMock {
  public ready(): any {
    return new Promise((resolve: Function) => {
      resolve();
    });
  }
}

export class MenuMock {
  public close(): any {
    return new Promise((resolve: Function) => {
      resolve();
    });
  }
}

If you take the PlatformMock as an example, this mocks that Platform service that Ionic provides. The real Platform service would have more complicated code that would integrate with the device to check when the device is ready, but the mock just converts that into a promise that instantly resolves. This will allow us to test code that makes use of Platform, keeping the asynchronous nature of that call, without relying on the service itself. In a unit test, we aren’t interested in testing if the Platform works, so we assume that it does and leave it out by creating a mock.

Create a file at src/polyfills.ts and add the following:

// This file includes polyfills needed by Angular 2 and is loaded before
// the app. You can add your own extra polyfills to this file.
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/set';
import 'core-js/es6/reflect';

import 'core-js/es7/reflect';
import 'zone.js/dist/zone';

2.5 Set up Test Configuration

Create a file at src/test.ts and add the following:

import './polyfills.ts';

import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { getTestBed, TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { App, Config, Form, IonicModule, Keyboard, DomController, MenuController, NavController, Platform } from 'ionic-angular';
import { ConfigMock } from './mocks';

// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare var __karma__: any;
declare var require: any;

// Prevent Karma from running prematurely.
__karma__.loaded = function (): void {
  // noop
};

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting(),
);

// Then we find all the tests.
let context: any = require.context('./', true, /\.spec\.ts/);

// And load the modules.
context.keys().map(context);

// Finally, start Karma to run the tests.
__karma__.start();

Create a file at src/tsconfig.test.json and add the following:

{
  "compilerOptions": {
    "baseUrl": "",
    "declaration": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": ["es6", "dom"],
    "mapRoot": "./",
    "module": "es6",
    "moduleResolution": "node",
    "outDir": "../dist/out-tsc",
    "sourceMap": true,
    "target": "es5",
    "typeRoots": [
      "../node_modules/@types"
    ]
  }
}

2.6 Add the Test Command

In order to trigger this whole testing process, we need to add a command that we can run through the command line.

Add the following entry to the scripts object in package.json

"test": "ng test"

Now whenever we run the following command:

npm test

it will run all of our tests. To ensure that everything is set up correctly, if you run the command now you should see the following result:

Testing with Jasmine and Karma

We don’t have any tests yet, though, so let’s get into building the tests themselves now.

3. Create a Test for the Root Component

With the current testing configuration we have, it is going to find any files in our project that have spec in the name, and treat those as tests to be run. This works quite well for organising code in the application because each test file can live alongside the file that is being tested.

We are going to create a test for the root component, so to do that we will need to create a app.spec.ts file and we will place it in the same directory as the app.component.ts file which is being tested.

Create a file at src/app/app.spec.ts and add the following:

import { TestBed, ComponentFixture, async } from '@angular/core/testing';
import { IonicModule } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

let comp: MyApp;
let fixture: ComponentFixture<MyApp>;

describe('Component: Root Component', () => {

    beforeEach(async(() => {

        TestBed.configureTestingModule({

            declarations: [MyApp],

            providers: [

            ],

            imports: [
                IonicModule.forRoot(MyApp)
            ]

        }).compileComponents();

    }));

    beforeEach(() => {

        fixture = TestBed.createComponent(MyApp);
        comp    = fixture.componentInstance;

    });

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

    it('is created', () => {

        expect(fixture).toBeTruthy();
        expect(comp).toBeTruthy();

    });

    it('initialises with a root page of HomePage', () => {
        expect(comp['rootPage']).toBe(HomePage);
    });

});

We first describe our test suite giving it a title of Component: Root Component. Inside of that test suite, we add our beforeEach blocks. The first one is what configures TestBed for us with the appropriate dependencies required to run the component. It is also worth noting that this beforeEach is using an async parameter which will allow us to use the asynchronous compileComponents method.

The second beforeEach will trigger once the TestBed configuration has finished. We use this block to create a reference to our fixture (which references the testing environment TestBed creates) and a reference to the actual component to be tested, which we store as comp. Then in the afterEach we clear all of these references.

As I mentioned before, beforeEach will run before every test and afterEach will run after every test, so since we have two tests in this file, the process would look something like this:

  1. Create TestBed testing environment
  2. Set up references
  3. Run is created test
  4. Clean up references
  5. Create TestBed testing environment
  6. Set up references
  7. Run initialises with a root page of HomePage test
  8. Clean up references

Our two tests here are quite simple. The first test just makes sure that the fixture and comp have been created successfully, and the second test checks that the component has its rootPage member variable set to the HomePage component.

If you were to run npm test now, you should see a result like this:

Testing with Jasmine and Karma

4. Create a Test for the Home Page

We’re going to set up a separate test for the Home Page component now. It is going to be very similar, but we are going to do a slightly more complex test.

Create a file at src/pages/home/home.spec.ts and add the following:

import { TestBed, ComponentFixture, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { MyApp } from '../../app/app.component';
import { HomePage } from './home';

let comp: HomePage;
let fixture: ComponentFixture<HomePage>;
let de: DebugElement;
let el: HTMLElement;

describe('Page: Home Page', () => {

    beforeEach(async(() => {

        TestBed.configureTestingModule({

            declarations: [MyApp, HomePage],

            providers: [

            ],

            imports: [
                IonicModule.forRoot(MyApp)
            ]

        }).compileComponents();

    }));

    beforeEach(() => {

        fixture = TestBed.createComponent(HomePage);
        comp    = fixture.componentInstance;

    });

    afterEach(() => {
        fixture.destroy();
        comp = null;
        de = null;
        el = null;
    });

    it('is created', () => {

        expect(fixture).toBeTruthy();
        expect(comp).toBeTruthy();

    });

    it('initialises with a title of My Page', () => {
        expect(comp['title']).toEqual('My Page');
    });

    it('can set the title to a supplied value', () => {

        de = fixture.debugElement.query(By.css('ion-title'));
        el = de.nativeElement;  

        comp.changeTitle('Your Page');
        fixture.detectChanges();
        expect(comp['title']).toEqual('Your Page');
        expect(el.textContent).toContain('Your Page');

    });

});

Much of this test is the same, except that we are also setting up references for a DebugElement and a HTMLElement, and the third test is a little bit more complex.

This page is going to provide the ability to change the title of the page through a function, and we want to test that it works. To do that, we want to check two things when this function is called:

  1. That the title member variable gets changed to the appropriate value
  2. That the interpolation set up to render the title inside of the <ion-title> tag updates in the DOM correctly

To do that, we grab a reference to the DOM element by querying the fixture using the CSS selector ion-title. We then call the changeTitle method on the component with a test value, and call detectChanges() to trigger change detection. Remember that by default change detection is not automatic when using TestBed, so if we did not manually trigger change detection here then the test would never run successfully.

To test that the component is now in the correct state, we check that the title member variable has been updated to Your Page and we also check that the content of <ion-title> in the DOM has been updated to reflect Your Page. Just because the member variable has been updated it doesn’t necessarily mean that the title would display correctly in the template, perhaps we could have spelled the binding wrong in the template, so it is important to check the element itself for this test to be accurate.

If you run npm test now the second two tests should fail because we haven’t actually implemented that functionality yet:

Testing with Jasmine and Karma

Let’s fix that.

Modify src/pages/home/home.ts to reflect the following:

import { Component } from '@angular/core';

@Component({
  selector: 'page-home',
  templateUrl: './home.html'
})
export class HomePage {

    title: string = "My Page";

    constructor() {

    }

    changeTitle(title){
        this.title = title;
    }

}

We will also need to set up the interpolation for title in the template.

Modify src/pages/home/home.html to reflect the following:

<ion-header>
  <ion-navbar>
    <ion-title>
      {{title}}
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  The world is your oyster.
  <p>
    If you get lost, the <a href="http://ionicframework.com/docs/v2">docs</a> will be your guide.
  </p>
</ion-content>

Now if you run the tests again using npm test you should see them pass:

Testing with Jasmine and Karma

Beautiful green text!

Summary

This post only scratches the surface of using TestBed to test Ionic 2 applications. There is a variety of different types of tests you will need to learn how to implement, but this should at least get you started on the right path. I will be following up this tutorial with many more in the future which will cover various aspects of testing.

What to watch next...

  • james

    How can I drug a menu in ionic 2?

    • Chanoch

      what does this mean?

      • james

        Hi Chanoch. I mean swiping back.

      • Chanoch

        In your app.html add the following attribute and value to the

        (capital B, capital E)

        That should do it. I think the default behaviour might be platform specific because this is supposed to be on by default in ios (but my iphone is broken) where as I need to add this specifically for android to support it.

      • james

        It is already there but it is not working on samsung android 4.4.2

      • james

        And why am I getting white screen just after splash in ionic 2 on a mobile device? Thanks

      • Chanoch

        Hi James, sorry for the long delay in responding. It sounds to me like you have a bug and it’s preventing the rendering of the screen. Does the white screen after splash not appear in a browser? If you want further help (for what it is worth – I am no expert) then you would need to share your code (perhaps on github?)

  • Chanoch

    The how to unit test an ionic 2 application is broken due to changes in angular 2. Worth avoiding as otherwise very confusing

    • It should work if you use the set up from this post instead, I’ll update that post shortly (will probably just reference this post for the test set up for all testing posts)

      • Chanoch

        Thanks Josh. I copied the instructions exactly (twice from scratch to be on the safe side) but am getting the message:

        “You have to be inside an angular-cli project in order to to use the test command”

        Any idea?

      • You might need to install angular-cli locally, if you install: npm install angular-cli –save-dev in your project does it work?

      • Chanoch

        Thanks, that got me much further. It seems I’ve got versions which are later than yours and which have breaking changes.

  • Trost

    Hi Josh! When I install the npm packages, I get a bunch of peer-dependency issues with @ngtools/webpack1.2.3 requiring a peer of some of the @angular packages (compiler, compiler-cli, core, [email protected]^2.3.0, and [email protected]^0.5.0). I googled a bit, but couldn’t find solution that worked. Did you have that problem, too? How did you solve it?

  • Ka Mok

    Very nice Josh. Good to see you’re making content for this. The only other one I know was lathonez’s clicker project. I’d be very interested to see you tackle testing HTTP. More specifically, having the ability to mock existing HTTP requests made to a real server in your tests with fake responses, mocking both the successful use cases and the unsuccessful ones.

  • Pete

    Has anyone else experienced the error
    ‘Failed: Unexpected value ‘MyService’ imported by the module ‘DynamicTestModule’ (rest of call stack omitted) ?

    MyService is a very simple provider (I am using as a test) with no dependencies at all, and I just cannot get it to work. I have checked everything above. I have tried added MyService this to each of imports, declarations, providers sections of the beforeEach(async(() => { …, but nothing :

    If I remove it from all of these arrays, I get “Error: Cannot create the component MyService as it was not imported into the testing module! 🙁

  • Tushar Patekar

    i am getting Cannot read property ‘newLine’ of undefined error after running npm test. @Josh can you please post your package.json

    • Tushar Patekar

      oops didn’t see that comment for repo below. thanks for the repo Josh 🙂

    • O L

      Could you find why this happens?, I tried to run my tests with angular-cli, because with karma-typescript i never can get the injection done, but now i am stuck in this “newLine” issue. Please let me know what you did. Thanks.

    • Ryan Pays

      Am getting this error as well. any fix?

    • Stefdelec

      I got the same error… I tried a lot of things, I don’t know where it is coming from…

    • Aivaras

      i removed old angular-cli, updated wiht @agnular/[email protected]
      updated references in karma.config.js
      as well structure of angular-cli.json change as well. Main changes:
      “environmentSource”: “environments/environment.ts”,
      “environments”: {
      “dev”: “environments/environment.ts”,
      “prod”: “environments/environment.prod.ts”
      }

      as well “main”: “app/main.ts” from “main”: “main.ts”,

      it works like a charm now.

  • Max Maeuschen

    I’m very interested in testing Ionic 2 applications, but I get the following error:
    Uncaught TypeError: Cannot read property ‘ev’ of null at webpack:///~/ionic-angular/platform/dom-controller.js:112:0 <- src/test.ts:4943
    This error came up in a project created by my own following this tutorial and in a cloned project of yours.

    • Mikhail Sazonov

      Me too!

    • Brett G. Palmer

      I’m seeing the same error with ionic 2.1.18 and using the cloned test-bed project.

    • I’ve seen this, though I’m not actually sure what is causing it – it seems to be happening intermittently. I had the error a couple of times but it went away after saving / retesting.

      • Mikhail Sazonov

        I noticed that an error occurs if you add one more ‘it’ block. If there is only one test block, there is no error…..

      • Chanoch

        Josh – you might consider spitting out your ionic info as an easy way to check dependencies.

    • Chanoch

      This is happening to me too – even with a patch that was submitted to deal with this exact error. I believe someone is looking into it when it happens on a navigation event.

      It looks likely it is a bug relating to the scrollable area – perhaps the content area is not being reinitialised following a destroy event (on re-entry into the component)

    • Chanoch

      I resolved this by installing a snapshot that was released to fix the bug:

      npm install –save –save-exact [email protected]

      as per brandyscarney on github and with Felipe’s help: https://github.com/driftyco/ionic/issues/10186

  • npm install -g karma-cli && npm install angular-cli –save-dev && npm install codecov –save-dev && npm install jasmine-core –save-dev && npm install jasmine-spec-reporter –save-dev && npm install karma –save-dev && npm install karma-chrome-launcher –save-dev && npm install karma-jasmine –save-dev && npm install karma-mocha-reporter –save-dev && npm install karma-remap-istanbul –save-dev && npm install ts-node –save-dev && npm install tslint –save-dev && npm install tslint-eslint-rules –save-dev && && npm install @types/jasmine –save-dev && npm install @types/node –save-dev

    • Godzsák Dávid

      npm install -g karma-cli && npm install angular-cli codecov jasmine-core jasmine-spec-reporter karma karma-chrome-launcher karma-jasmine karma-mocha-reporter karma-remap-istanbul ts-node tslint tslint-eslint-rules @types/jasmine @types/node –save-dev

  • Chanoch

    Hi Josh, LeRoy,

    Very useful resource as usual. I really appreciate your blog entries and the book got me started very nicely. Couple of points:

    Did you know you can chain your installation to save typing?

    npm install codecov jasmine-core jasmine-spec-reporter karma karma-chrome-launcher … etc etc … –save-dev

    You
    don’t need to use karma init karma.conf.js if you are going to overwrite
    the file. All that does is walk you through the choices and creates the file accordingly. Just simply create the configuration file and away
    you go.

    • I like to keep the “normal” steps (like generating the file with the CLI) so people know that it is available, but I agree running the commands for installing the dependencies is a bit cumbersome, I will likely update this!

      • Chanoch

        Good call – makes sense.

  • Marcelo Burriel

    Hi, i have this error when run ‘npm test’:

    /home/marcelo/ionic2-testbed/node_modules/@ngtools/json-schema/src/schema-class-factory.js:47
    constructor(schema, value, …fallbacks) {
    ^^^

    SyntaxError: Unexpected token …
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:387:25)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Module.require (module.js:367:17)
    at require (internal/module.js:20:19)
    at Object. (/home/marcelo/ionic2-testbed/node_modules/@ngtools/json-schema/src/index.js:2:30)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    npm ERR! Test failed. See above for more details.

    Any solution??

    • Brane Vrajich

      What version of node are you running? The token “…” looks like it’s rest parameter or spread operator which is a new es6 feature added to Node v6. If you are running anything before v6 try updating node and see if that works.

  • Should I need to change/update the angular cli? I get this warning:

    As a forewarning, we are moving the CLI npm package to “@angular/cli” with the next release, which will only support Node 6.9 and greater. This package will be officially deprecated shortly after.

    I am using Node 6.9.1

  • Ethan Melamed

    Hello,

    I’ve added all of the fiels and their content but I get stuck on running >npm test.

    Here’s the error I get:

    Cannot read property ‘config’ of null
    TypeError: Cannot read property ‘config’ of null
    at /Users/ethan/programming/debugging/dailyreportclient/node_modules/angular-cli/tasks/test.js:12:76
    at Class.run (/Users/ethan/programming/debugging/dailyreportclient/node_modules/angular-cli/tasks/test.js:10:16)
    at Class.run (/Users/ethan/programming/debugging/dailyreportclient/node_modules/angular-cli/commands/test.js:29:25)
    at Class. (/Users/ethan/programming/debugging/dailyreportclient/node_modules/angular-cli/ember-cli/lib/models/command.js:134:17)
    at process._tickCallback (internal/process/next_tick.js:103:7)
    npm ERR! Test failed. See above for more details.

    Here is the line specified in the error:

    //test.js:12:76
    karmaConfig = path.join(projectRoot, _this.project.ngConfig.config.test.karma.config);

    Please help!!

  • Hey Josh,
    still trying to understand the setup in detail. If I use it just like explained in this article, will any of the testing components / code being used for the production build of the app? If so, how could I avoid this in order to keep my app as small as possible?

  • Roman Bolkhovitin

    Hello! Could you help me, please. Do you have any idea why I’ve got “No provider for StatusBar” Error on step 3?
    Unfortunately I’m not enought familiar with Ionic and Angular2.

  • Rachna Adwani

    Hi Josh, I tired out the setup procedure and I am stuck at the first “npm test” itself. Getting the following error . Can you please help me figuring out where possibly I am going wrong.

    > [email protected] test /Users/r1/Desktop/FI/Project
    > ng test

    /Users/r1/Desktop/FI/Project/node_modules/@ngtools/json-schema/src/schema-class-factory.js:34
    result.push(…indices);
    ^^^

    SyntaxError: Unexpected token …
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:373:25)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Module.require (module.js:353:17)
    at require (internal/module.js:12:17)
    at Object. (/Users/radwani/Desktop/FI/JCPenney/node_modules/@ngtools/json-schema/src/index.js:3:30)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)
    npm ERR! Test failed. See above for more details.

  • YD Hettiarachchi

    I got this error
    Error: Version of @angular/compiler-cli needs to be 2.3.1 or greater. Current version is “undefined”.

    any suggestions?

  • Tom van Eijk

    Can you please update step 2 in this instruction? Step 2 seems to be outdated and you refer to step 2 in lot of your post. Thanks!

    • Roman Bolkhovitin

      Hello. Looks like Josh doesn’t follow for comments in this topic anymore. Could you please show the proper way for set up Tests?

  • Queau Jean Pierre

    Josh, As you mentioned it was updated on April 5, did you developed this test procedure with ionic 3 and angular 4 ?
    As when i go through at step 3 create a test for root component when i ran npm test, i got the following error (both on Windows and Mac)

    Any idea?

    START:
    Component: Root Component
    × is created
    × initialises with a root page of HomePage

    Finished in 0.432 secs / 0.534 secs @ 08:53:27 GMT+0
    200 (Paris, Madrid (heure d’été))

    SUMMARY:
    √ 0 tests completed
    × 2 tests failed

    FAILED TESTS:
    Component: Root Component
    × is created
    Chrome 57.0.2987 (Windows 10 0.0.0)
    Error
    at injectionError (webpack:///~/@angular/cor
    e/@angular/core.es5.js:1232:21 <- src/test.ts:1927:8
    6) [angular]
    at noProviderError (webpack:///~/@angular/co
    re/@angular/core.es5.js:1270:0 <- src/test.ts:1965:1
    2) [angular]
    at ReflectiveInjector_._throwOrNull (webpack
    :///~/@angular/core/@angular/core.es5.js:2772:0 <- s
    rc/test.ts:3467:19) [angular]
    at ReflectiveInjector_._getByKeyDefault (web
    pack:///~/@angular/core/@angular/core.es5.js:2811:0
    <- src/test.ts:3506:25) [angular]
    at ReflectiveInjector_._getByKey (webpack://
    /~/@angular/core/@angular/core.es5.js:2743:0 <- src/
    test.ts:3438:25) [angular]
    at ReflectiveInjector_.get (webpack:///~/@an
    gular/core/@angular/core.es5.js:2612:0 <- src/test.t
    s:3307:21) [angular]
    at DynamicTestModuleInjector.NgModuleInjecto
    r.get (webpack:///~/@angular/core/@angular/core.es5.
    js:3577:0 <- src/test.ts:4272:52) [angular]
    at resolveDep (webpack:///~/@angular/core/@a
    ngular/core.es5.js:10981:0 <- src/test.ts:11676:45)
    [angular]
    at createClass (webpack:///~/@angular/core/@
    angular/core.es5.js:10844:0 <- src/test.ts:11539:91)
    [angular]
    at createDirectiveInstance (webpack:///~/@an
    gular/core/@angular/core.es5.js:10675:21 <- src/test
    .ts:11370:37) [angular]
    at createViewNodes (webpack:///~/@angular/co
    re/@angular/core.es5.js:12024:33 <- src/test.ts:1271
    9:49) [angular]
    at createRootView (webpack:///~/@angular/cor
    e/@angular/core.es5.js:11929:0 <- src/test.ts:12624:
    5) [angular]
    at callWithDebugContext (webpack:///~/@angul
    ar/core/@angular/core.es5.js:13060:25 <- src/test.ts
    :13755:42) [angular]
    at Object.debugCreateRootView [as createRoot
    View] (webpack:///~/@angular/core/@angular/core.es5.
    js:12521:0 <- src/test.ts:13216:12) [angular]

  • Queau Jean Pierre

    Josh,
    found the solution
    in app.spec.ts
    add the following:
    import { BrowserModule } from ‘@angular/platform-browser’;
    import { StatusBar } from ‘@ionic-native/status-bar’;
    import { SplashScreen } from ‘@ionic-native/splash-screen’;

    and change beforeEach as follows:

    beforeEach(async(() => {

    TestBed.configureTestingModule({

    declarations: [MyApp],

    providers: [
    StatusBar,
    SplashScreen
    ],

    imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
    ]

    }).compileComponents();

    }));

    • Maxime Israel

      Thanks a lot, that was a great catch. I hope Josh updates the guide!

    • It worked for me too! Thanks a lot!

    • Toupic

      Thanks Jean Pierre 😀

  • Queau Jean Pierre

    Following up, a more correct answer should be in using mock
    so adding the following definition in the mocks.ts file
    export class StatusBarMock {
    public styleDefault(): void {}
    }
    export class SplashScreenMock {
    public hide(): void {}
    public show(): void {}
    }

    and then modifying the app.spec.ts
    import { StatusBarMock, SplashScreenMock } from ‘../mocks’
    and
    providers: [
    {provide:StatusBar, useClass:StatusBarMock},
    {provide:SplashScreen, useClass:SplashScreenMock}
    ],

  • Balog Dominik

    Hi I got these warnings:
    WARNING in ./~/@angular/core/@angular/core.es5.js
    5889:15-36 Critical dependency: the request of a dependency is an expression

    WARNING in ./~/@angular/core/@angular/core.es5.js
    5905:15-102 Critical dependency: the request of a dependency is an expression

    WARNING in ./~/ionic-angular/util/ng-module-loader.js
    54:11-36 Critical dependency: the request of a dependency is an expression

    WARNING in ./~/ionic-angular/util/ng-module-loader.js
    69:11-36 Critical dependency: the request of a dependency is an expression

    And also these errors at FAILED TESTS:
    https://uploads.disquscdn.com/images/7afab6996294749607afa1822914bcd8f533b7236746b6f9893513e79b6e210b.png

  • Jacob Tucker

    I am not getting an output to my coverage, ie it’s not being created, folder. Has anyone gotten this to work?

    • Jacob Tucker

      Now I’m getting the folder but there are no contents in the generated files.

  • Jadir J. Silva Jr.

    Hi, can you update to “@angular/cli”, I have a error about length and I dont find anything in the web for that.

    C:UsersjadirDocumentsreposionic2-testbed (master)
    λ npm test

    > [email protected] test C:UsersjadirDocumentsreposionic2-testbed
    > ng test

    Cannot read property ‘length’ of undefined
    TypeError: Cannot read property ‘length’ of undefined
    at createSourceFile (C:UsersjadirDocumentsreposionic2-testbednode_modulestypescriptlibtypescript.js:14876:109)
    at parseSourceFileWorker (C:UsersjadirDocumentsreposionic2-testbednode_modulestypescriptlibtypescript.js:14808:26
    )
    at Object.parseSourceFile (C:UsersjadirDocumentsreposionic2-testbednode_modulestypescriptlibtypescript.js:14757:2
    6)
    at Object.createSourceFile (C:UsersjadirDocumentsreposionic2-testbednode_modulestypescriptlibtypescript.js:14612:
    29)
    at new TypeScriptFileRefactor (C:[email protected]ksrcrefactor.js
    :29:35)
    at Object.resolveEntryModuleFromMain (C:[email protected]ksrcentr
    y_resolver.js:105:20)
    at AotPlugin._setupOptions (C:UsersjadirDocumentsre[email protected]:143:
    50)
    at new AotPlugin (C:[email protected]ksrcplugin.js:26:14)
    at _createAotPlugin (C:[email protected]elswebpack-configstypesc
    ript.js:55:12)
    at Object.exports.getNonAotTestConfig (C:[email protected]elswebp
    ack-configstypescript.js:103:19)
    at WebpackTestConfig.buildConfig (C:[email protected]elswebpack-t
    est-config.js:17:31)
    at init (C:[email protected]ginskarma.js:79:94)
    at Array.invoke (C:UsersjadirDocumentsreposionic2-testbednode_modulesdilibinjector.js:75:15)
    at Injector.get (C:UsersjadirDocumentsreposionic2-testbednode_modulesdilibinjector.js:48:43)
    at C:UsersjadirDocumentsreposionic2-testbednode_moduleskarmalibserver.js:143:20
    at Array.forEach (native)
    npm ERR! Test failed. See above for more details.

    • David P!

      I hope this helps you: https://github.com/angular/angular-cli/issues/5053#issuecomment-283951922

      I had the same problem and in my case I solved it this way at angular-cli.json file:

      changing this incorrect url
      “main”: “main.ts”
      to
      “main”: “app/main.ts”,

      • David P!

        I still had a problem:
        “ERROR in Could not resolve module @angular/router”

        Solution:
        – npm install @angular/[email protected] –save
        or
        – Manually add “@angular/router”: “4.1.0” via package.json and do npm install

        (Note that the version must match the other angular packages installed)

  • Cannot find name ‘describe’.

    • crazytodde

      put:

      import { } from “jasmine”;

      at the top of your spec file

  • Erik Johnson

    I’ve created a more-or-less updated (for @angular/cli, etc.) version of this guide: http://erikaugust.com/thoughts/the-guide-to-unit-testing-in-ionic-2/

  • Diego

    Not working for me 🙁 In step 3, when I execute “npm test”, the newly created test (app.spec.ts) is not being detected. The output summary is still: 0 test completed…

  • johncclayton

    Hi Josh – i’m on Windows and when I run the npm test command – I get the following:

    03 07 2017 22:28:12.074:ERROR [preprocess]: Can not load “webpack”!
    WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a co
    nfiguration object that does not match the API schema.
    – configuration.context: The provided value “D:/src/roasted” is not an absolute path!
    at webpack (D:srcroastednode_moduleswebpacklibwebpack.js:19:9)
    at new Plugin (D:srcroastednode_moduleskarma-webpacklibkarma-webpack.js:63:18)
    at invoke (D:srcroastednode_modulesdilibinjector.js:75:15)
    at Array.instantiate (D:srcroastednode_modulesdilibinjector.js:59:20)
    at get (D:srcroastednode_modulesdilibinjector.js:48:43)
    at D:srcroastednode_modulesdilibinjector.js:71:14
    at Array.map (native)
    at Array.invoke (D:srcroastednode_modulesdilibinjector.js:70:31)
    at Injector.get (D:srcroastednode_modulesdilibinjector.js:48:43)
    at instantiatePreprocessor (D:srcroastednode_moduleskarmalibpreprocessor.js:55:20)
    at D:srcroastednode_moduleskarmalibpreprocessor.js:106:17
    at Array.forEach (native)
    at D:srcroastednode_moduleskarmalibpreprocessor.js:103:27
    at module.exports (D:srcroastednode_moduleskarmanode_modulesisbinaryfileindex.js:28:12)
    at D:srcroastednode_moduleskarmalibpreprocessor.js:84:7
    at D:srcroastednode_modulesgraceful-fsgraceful-fs.js:78:16
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:439:3)
    03 07 2017 22:28:12.078:WARN [karma]: No captured browser, open http://localhost:9876/
    03 07 2017 22:28:12.087:INFO [karma]: Karma v1.7.0 server started at http://0.0.0.0:9876/
    03 07 2017 22:28:12.087:INFO [launcher]: Launching browser Chrome with unlimited concurrency
    03 07 2017 22:28:12.088:ERROR [karma]: Found 1 load error
    npm ERR! Test failed. See above for more details.

    I searched around for about 45 minutes looking for various solutions – to no avail, mostly uninstalling / re-installing webpack and webpack-dev-server – seems like a *horrible* mess to me.

    Has anyone encountered this before?

    Running webpack.cmd on its own produces this (which is accurate, there is no webpack.config.js):

    PS D:srcroasted> webpack.cmd
    No configuration file found and no output filename configured via CLI option.
    A configuration file could be named ‘webpack.config.js’ in the current directory.
    Use –help to display the CLI options.

    Thanks!

    • Aric M

      Did you find a solution? I have the same issue.

      • crazytodde

        In the end I found that the problem was that angular-cli needs to be changed to the new @angular/cli package

    • Joe Badaczewski

      I am not sure of specifics, but as soon as I installed webpack locally, i was able to run npm test.

      npm install [email protected] –save-dev

      I used this specific version because of this thread: https://github.com/angular/angular-cli/issues/2234

      Just using npm install webpack did not work for me

    • crazytodde

      I tried:
      npm install [email protected] –save-dev
      npm install webpack

      neither worked but this did:

      npm install webpack –save-dev

  • Sam

    When I type “ng test”, I get the following error message in the console :

    You have to be inside an angular-cli project in order to use the test command.

    Not sure what I’ve missed… ?

    • Sam

      nevermind.. I needed to install @angular/cli globally.

  • Sam

    the line public static beforeEachCompiler(components: Array) generates the following error when I run “ng test” :
    Generic type ‘Array’ requires 1 type argument(s).

    I can’t figure out why. Anyone ran into this issue ?

  • Bex

    Thanks so much. That was very quick and mostly painless.

  • Khalav

    Configured my app replacing the old “angular-cli” to “@angular/cli” .
    After creating the first test, the compiler doesn’t detect it and it runs 0 tests.

    What should I do?