Mainly Devel Notes

Twitter, GitHub, StackOverflow: @ovrmrw (short hand of "overmorrow" that means the day after tomorrow)

Testing(Jasmine) with DI on Angular2

Angular2, Dependency Injection, Test(Jasmine), TypeScript

(This post is English version of Angular2でDI(依存性注入)してテスト(Jasmine)を書いてみた。)

I've learned about DI of Angular2 and Test at the official tutorials below.

5 MIN QUICKSTART
TUTORIAL: TOUR OF HEROES
TESTING GUIDES

If you read all of them, you will make sense well what I talking about in this post. I strongly recommend it.
This is a follow-up part of Angular2 for TypeScript official tutorial - and - some additional playings.

Ok then, I introduce you how to write the tests of Angular2 with DI.

Be prepared

npm init -y
npm install angular2@2.0.0-alpha.47 --save --save-exact 
npm install systemjs jquery --save
npm install live-server typescript jasmine-core --save-dev
tsc --init
tsd install jquery --save

Create directories

mkdir src
cd src
mkdir app

In this case, we're along with the Angular2 tutorial's directories structure.

Edit "scripts" in package.json

"scripts": {
  "tsc": "./node_modules/.bin/tsc -p . -w",
  "test": "live-server --open=src/index.test.html"
},

Edit tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES5",
    "noImplicitAny": false,
    "sourceMap": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  },
  "exclude": [
    "node_modules"
  ]
}

Create a file, (root)/src/system.config.js

System.config({
  baseURL: '.',
  packages: {
    'app': { defaultExtension: 'js' }
  }
});

If your config doesn't run correctly even though you think you wrote it perfectly, please look at window.System object on a debugger tool. It'll help you so much.

Create a file, (root)/src/index.test.html

<html>
  <head>
    <title>Angular 2 Test</title>
    <link rel="stylesheet" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
    <script src="../node_modules/systemjs/dist/system.src.js"></script>             
    <script src="../node_modules/angular2/bundles/angular2.dev.js"></script>    
    <script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
    <script src="../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
    <script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
    <script src="system.config.js"></script>      
  </head>
  <body>    
    <script>
      //System.import('app/app');
      System.import('app/app.spec').then(window.onload);
    </script>
    <my-app>loading...</my-app>
    <script src="../node_modules/jquery/dist/jquery.min.js"></script>
  </body>
</html>

Create a file, (root)/src/app/app.ts

import {bootstrap, Component} from 'angular2/angular2';
import {HeroService} from './hero-service';

@Component({
  selector: 'my-app',
  template: `
    <ul>
      <li *ng-for="#hero of heroes" id="hero{{hero.id}}">{{hero.name | uppercase}}</li>
    </ul>
    <div id="count">{{countStr + heroesCount | uppercase}}</div>
  `
})
export class AppComponent {
  heroes: Hero[];
  countStr = 'count:';
  constructor(heroService: HeroService){
    this.heroes = heroService.getHeroes();
  }
  get heroesCount(): number {
    return this.heroes.length;
  }
}
bootstrap(AppComponent, [HeroService]);

Just only my understanding, how to write these constructor() and bootstrap() is the basic way of Angular2 DI.
I'll tell you about HeroService soon later.

Create a file, (root)/src/app/my.d.ts

declare interface Hero {
  id: number,
  name: string
}

declare interface IHeroService {
  getHeroes: () => Hero[]
}

Create a file, (root)/src/app/hero-service.ts

export class HeroService implements IHeroService {
  heroes: Hero[];
  constructor() {
    this.heroes = HEROES;
  }
  getHeroes() {
    return this.heroes;
  }
}

var HEROES: Hero[] = [
  { "id": 11, "name": "Mr. Nice" },
  { "id": 12, "name": "Narco" },
  { "id": 13, "name": "Bombasto" },
  { "id": 14, "name": "Celeritas" },
  { "id": 15, "name": "Magneta" },
  { "id": 16, "name": "RubberMan" },
  { "id": 17, "name": "Dynama" },
  { "id": 18, "name": "Dr IQ" },
  { "id": 19, "name": "Magma" },
  { "id": 20, "name": "Tornado" }
];

getHeroes() is the important function for DI.
That is called by constructor() in app.ts.

Create a file, (root)/src/app/app.spec.ts [THE MAIN PART OF THIS POST]

import {bootstrap, Component, provide} from 'angular2/angular2';
import {HeroService} from './hero-service';
import {AppComponent} from './app';
//import 'jquery'; // Rejected because of error at runtime
declare var $: JQueryStatic;

describe('Mock Test', () => {
  let ac: AppComponent;
  let mock: IHeroService = {
    getHeroes: () => [{ id: 1, name: 'mock1' }, { id: 2, name: 'mock2' }]
  }

  beforeEach(done => {
    bootstrap(AppComponent, [provide(HeroService, { useValue: mock })])
      .then(result => result.instance)
      .then(instance => {
        ac = instance;
        done();
      });
  });

  it("Test for Heroes' Name", () => {
    expect(ac.heroes[0].name).toEqual('mock1');
    //expect(document.querySelector('#hero' + ac.heroes[0].id).textContent).toEqual('MOCK1');
    expect($(`#hero${ac.heroes[0].id}`).text()).toEqual('MOCK1');
  });

  it("Test for Heroes' Count", () => {
    expect(ac.heroesCount).toEqual(2);
    //expect(document.querySelector('#count').textContent).toEqual('COUNT:2');
    expect($('#count').text()).toEqual('COUNT:2');
  });
});

Importing app.ts and hero-service.ts, then injecting the mock object.
Accordingly, ten heroes are overwritten by two mock heroes.
(the second arg of bootstrap() is that proccess)
After that, retreiving the instance of AppComponent from the return object of bootstrap(), then testing it.
The all of these tests will be successed, so it tells that View and ViewModel of MVVM are as working correctly as we expected.
One more thing,
Due to not JavaScript but TypeScript, IntelliSense and Compiler Error Check are available on the variable ac. That is important.
Again, that is very important especially when you maintain them.

Finally Run the tasks and See the results

npm run tsc
npm test

Thanks for your reading.