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.