The easiness of testing on Angular 2
Wade HuangWriting unit tests on Angular 2 is the easiest tests I have written amount many platforms like .Net, Android, NodeJs. I think it is because its good DNA. Angular 2 has build-in dependency injection and build-in mock utilities and tooling to help you write test.
Tooling
By default, when you use ng new {project_name}
to create a new project, the template includes the environment of its test frameworks.
Angular 2 uses two test frameworks:
Use npm test
or ng test
to start karma. Then it will launch a browser and run the tests. and re-run tests every time you make a changes. (Karma’s idea is write and run test immediately, which is a good idea but needs time to be adopted).
Furthermore, when you use ng g c {component_name}
or ng generate component {component_name}
or ng g s {service_name}
or ng generate service {service_name}
to create components or services, the cli also generates {name}.spec.ts files for you. So you don’t need to create test files from scratch.
There are four examples to show you how easy to write a test on Angular 2:
Example 1: Inject Stub Service.
In this example, you can see how to use replace AuthService by AuthServiceStub.
// home.component.ts
@Component({
selector: 'app-home',
templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
private isLoggedIn: boolean;
//the injection will inject AuthService for HomeComponent
constructor(private authService: AuthService) {
}
ngOnInit() {
this.authService
.isLoggedIn()
.then((result) => {
this.isLoggedIn = result;
});
}
}
// home.component.spec.ts
class AuthServiceStub {
// add a AuthServiceStub which always return true;
isLoggedIn(): Promise<boolean> {
return Promise.resolve(true);
}
}
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
// definition the dependency injection for module
TestBed.configureTestingModule({
declarations: [HomeComponent],
// the injection will use AuthServiceStub for AuthService when creates HomeComponent
providers: [{provide: AuthService, useValue: new AuthServiceStub()}]
})
.compileComponents();
}));
it('should do something if isLoggedIn', () => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
expect(component).toBeTruthy();
});
});
Example 2: Override Component Properties.
In this example, you can see how to override the template of HomeComponent. Because HomeComponent has many sub-components and sub-components use many other services, so if we use original template we need to mock many services which increases the difficulty of making a test. (in order languages, override the view to make test the controller and the view easier).
const template = `
<div id="root" [ngSwitch]="isLoggedIn">
<h1 *ngSwitchCase="true">Template 1</h1>
<h1 *ngSwitchCase="false">Template 2</h1>
</div>
`;
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
// override the template of the HomeComponent
TestBed.configureTestingModule({
declarations: [HomeComponent]
})
.overrideComponent(HomeComponent, {set: {template: template}})
.compileComponents();
}));
it('should use template 1 if isLoggedIn', () => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
component["isLoggedIn"] = true;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css("h1")).nativeElement.textContent).toBe("template 1");
});
it('should use template 2 if not isLoggedIn', async(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
component["isLoggedIn"] = false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css("h1")).nativeElement.textContent).toBe("template 2");
}));
});
Example 3: Mock Router.
In this example, you can see how to use build-in RouterTestingModule to test router.
const routes: Routes = [
{
path: '',
data: {name: "home"},
component: AppMockComponent,
},
{
path: 'login',
component: MockComponent
},
{
path: 'signup',
component: MockComponent
},
{
path: 'profile',
component: MockComponent
},
{path: '**', redirectTo: '/', pathMatch: 'full'},
];
describe('AppRouting', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AppMockComponent, MockComponent],
imports: [
// use RouterTestingModule for testing router
RouterTestingModule.withRoutes(routes)
],
providers: []
})
.compileComponents();
}));
it('common routing test', async(() => {
const fixture = TestBed.createComponent(AppMockComponent);
const router: Router = TestBed.get(Router);
return router
.navigate(["login"])
.then(() => {
expect(router.url).toBe("/login");
})
.then(x => router.navigate(["signup"]))
.then(() => {
expect(router.url).toBe("/signup");
})
.then(x => router.navigate(["profile"]))
.then(() => {
expect(router.url).toBe("/profile");
})
.then(x => router.navigate(["/"]))
.then(() => {
expect(router.url).toBe("/");
})
}));
});
Example 4: Mock Http Response.
In this example, you can see how to use build-in MockBackend to Mock Http Response. I love this feature because it makes api testing easier.
describe('ApiService', () => {
beforeEach(() => {
this.injector = ReflectiveInjector.resolveAndCreate([
{provide: ConnectionBackend, useClass: MockBackend},
{provide: RequestOptions, useClass: BaseRequestOptions},
Http,
ApiService
]);
this.apiService = this.injector.get(ApiService);
this.backend = this.injector.get(ConnectionBackend) as MockBackend;
});
it('should get a model', fakeAsync(() => {
this.backend.connections.subscribe((connection: MockConnection) => {
connection.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify({data: {attributes: {"prop1": "test"}}}),
})));
});
this.apiService.getData().then((model) => {
expect(model.prop1).toBe("test");
});
}));
});
You can visit its guide to find more useful features of testing.
Although many other frameworks can do the same things, but Angular 2’s test framework is build-in, so it provides many features and easy to write tests. And because the test framework is build-in, you don’t need to research other third-party frameworks and hesitate which one should be used and spent time to try them to your projects. Plus, new crews who know Angular 2 might know how to use the same test framework which is a default test framework for developers to learn.