Introduction
This Section only provides an Introduction to Unit Testing an Angular 2 App.
It’s NOT a detailed Guide about that neither is it a detailed Introduction to Unit Testing in general. Check out the “Further Resources” (last Lecture in this Module) to dive deeper into these topics.
Why units tests?
Analyzing the testing setup (as created by the CLI)
- In order to simulate we need to run the app in the testing environment as it would be in the browser.
- Our test runner will mount the testing environment and will read spec files to execute the tests.
- Lets see one example
- app/app.component.spec.ts
import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ AppComponent ], }).compileComponents(); })); it('should create the app', async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); })); it(`should have as title 'app'`, async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('app'); })); it('should render title in a h1 tag', async(() => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!!'); })); });
- In the beforeEach we configure the module for our testing with the TestBed utility.
- toBeTruthy check there is an existing app
- In the third test we check one value in the template. For that we need to
Running Tests (with the CLI)
ng test
Adding a Component and some fitting tests
- Lets create a new user component: ng g c user
- Lets create a basic test for this component: check if instance is existing
app/user/user.component.ts
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class UserComponent implements OnInit { user: {name: string}; isLoggedIn = false; constructor() { } ngOnInit() { } }
app/user/user.component.spec.ts
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { UserComponent } from './user.component'; describe('UserComponent', () => { let component: UserComponent; let fixture: ComponentFixture<UserComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ UserComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(UserComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should be created', () => { expect(component).toBeTruthy(); }); });
app/user/user.component.html
<div *ngIf="isLoggedIn"> <h1>User logged in</h1> <p>User is {{ user.name }}</p> </div> <div *ngIf="!isLoggedIn"> <h1>User not logged in</h1> <p>Please log in first</p> </div>
Testing Dependencies: Components and Services
- Lets add a service component
user.service.ts
export class UserService { user = { name: 'Max' }; }
user.component.spec.ts
it('should use the user name from the service', ()=> { let userService = fixture.debugElement.injector.get(UserService); fixture.detectChanges(); expect(userService.user.name).toEqual(component.user.name); }); it('should display the user name if user is logged in', ()=> { component.isLoggedIn = true; fixture.detectChanges(); let compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('p').textContent).toContain(component.user.name); }); it('should not display the user name if user is not logged in', ()=> { fixture.detectChanges(); let compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('p').textContent).not.toContain(component.user.name); });
Simulating Asyncs Tasks
- We create a data service to simulate a remote service
- In the test we wrap tests with async method and we use fixture.whenStable to check the assertions.
/shared/data.service.ts
export class DataService { getDetails() { const resultPromise = new Promise((resolve,reject)=>{ setTimeout(()=> { resolve('Data'); },1500); }); return resultPromise; } }
/user/user.component.ts
import { Component, OnInit } from '@angular/core'; import {UserService} from './user.service'; import {DataService} from '../shared/data.service'; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'], providers: [UserService, DataService] }) export class UserComponent implements OnInit { user: {name: string}; isLoggedIn = false; data:string; constructor(private userService: UserService, private dataService: DataService) { } ngOnInit() { this.user = this.userService.user; this.dataService.getDetails().then((data:string)=> this.data = data); } }
/user/user.component.spec.ts
it('shouldnt fetch data successfully if not called asynchronously', ()=> { let dataService = fixture.debugElement.injector.get(DataService); let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data')); fixture.detectChanges(); expect(component.data).toBe(undefined); }); it('should fetch data successfully if called asynchronously', async(()=> { let dataService = fixture.debugElement.injector.get(DataService); let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data')); fixture.detectChanges(); fixture.whenStable().then(()=> { expect(component.data).toBe('Data'); }); }));
Using “fakeAsync” and “tick”
- It is another way for testing and async feature:
it('should fetch data successfully if called asynchronously', async(()=> { let dataService = fixture.debugElement.injector.get(DataService); let spy = spyOn(dataService, 'getDetails').and.returnValue(Promise.resolve('Data')); fixture.detectChanges(); fixture.whenStable().then(()=> { expect(component.data).toBe('Data'); }); }));
Isolated vs Non-isolated tests
- There can be some services or pipes with functions not related to Angular. We just make simple tests for those
- For example a reverse pipe and its test
/shared/reverse.pipe.ts
import { Pipe } from '@angular/core'; @Pipe({ name: 'reverse' }) export class ReversePipe { transform(value: string) { return value.split("").reverse().join(""); } }
/shared/reverse.pipe.spec.ts
import { ReversePipe } from './reverse.pipe'; describe('ReversePipe', () => { it('should be created', () => { let reversePipe = new ReversePipe(); expect(reversePipe.transform('hello')).toEqual('olleh'); }); });
Further resources & Where to go next
This Module only provides a brief and basic Introduction to Angular 2 Unit Tests and the Angular 2 Testing Suite. This Course isn’t focused on Testing.
If you want to dive deeper, the official Docs actually are a great place to start. There you’ll also find a Non-CLI Setup!
Official Docs: https://angular.io/docs/ts/latest/guide/testing.html
I can also recommend the following Article: https://semaphoreci.com/community/tutorials/testing-components-in-angular-2-with-jasmine
For more Information on how to run Tests with the CLI have a look at their official Docs:
=> Unit Tests: https://github.com/angular/angular-cli#running-unit-tests
=> E2E Tests: https://github.com/angular/angular-cli#running-end-to-end-tests