📜 ⬆️ ⬇️

Angular2: RC4 to RC5 Unit Tests Migration Guide

image

I’ll say at once that I am not a fan of Angular1 , angular-way and others like them, because the guys from Angular have done such a thing that sometimes you wonder. Nevertheless, their new creation looks promising. Yes, America was not discovered, but they created something that could compete with popular modern frameworks (React + Redux, Aurelia, etc.).

There are pros and cons, about which articles and even books have already been written, but the essence of the post is different.
')
RC5 came out just a week ago and “pleased” the developers with many changes that, perhaps, help in the work and simplify life, but make them seriously sweat over rewriting the already written code.

There was no limit to my surprise when I found out that, having released a new version in rc5, the guys forgot to update the section with Testing, in which there was some useful information, and so “the cat wept”.

Since I could not find the information I was interested in, I had to figure it out. I hope the information will help those who are suffering right now over what is moving from rc4 to rc5 and his tests are written with such love. There will be neither configurations, nor huge pieces of code, and the information is designed for those who already know the basics of Angular2.

Let us estimate the basic structure of the application:
- app
- app.component.ts
- app.module.ts
- main.ts
- components
- table.component.ts
- services
- post.service.ts
- models
- post.model.ts
- test
- post.service.mock.ts
- table.component.spec.ts
- post.model.spec.ts
- post.service.spec.ts


Hereinafter I will use examples in TypeScript, because the code written on it, as for me, looks a bit livelier and more interesting. The example will describe the application that creates the table and draws it. Simple and understandable to clarify how to write tests now.

app.component is the first component to be loaded after the application is initialized.
// Angular import { Component } from '@angular/core'; // Services import {PostService} from './app/services/post.service'; import {Post} from './app/models/post.model'; @Component({ selector: 'app', template: ` <div *ngIf="isDataLoaded"> <table-component [post]="post"></table-component> </div> ` }) export class AppComponent { public isDataLoaded: boolean = false; public post: Post; constructor(public postService: PostService) {} ngOnInit(): void { this.postService.getPost().subscribe((post: any) => { this.post = new Post(post); this.isDataLoaded = true; }); } } 

app.module is an innovation in rc5 that stores all the dependencies of the module. In our case, it will provide PostService and TableComponent.

 import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HttpModule } from '@angular/http'; // Components import { AppComponent } from './app/app.component'; import {TableComponent} from './app/components/table/table.component'; // Services import {PostService} from './app/services/post.service'; @NgModule({ declarations: [ AppComponent TableComponent ], imports: [ BrowserModule, HttpModule ], providers: [ PostService ], bootstrap: [AppComponent] }) export class AppModule {} 

main is the entry point to the application that Webpack, SystemJS, etc. uses.

 import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(AppModule); 

table.component - the component that we want to draw.

 // Angular import {Component, Input} from '@angular/core'; @Component({ selector: 'table-component', template: `<table> <thead> <tr> <th>Post Title</th> <th>Post Author</th> </tr> </thead> <tbody> <tr> <td>{{ post.title}}</td> <td>{{ post.author}}</td> </tr> </tbody> </table>` }) export class TableComponent { @Input() public post: any; } 

post.service - Injectable service that makes API requests and pulls a post.

  import {Injectable} from '@angular/core'; import {Observable} from 'rxjs/Rx'; import {Post} from './app/models/post.model'; import { Http } from '@angular/http'; @Injectable() export class PostService { constructor(http: Http) {} public getPost(): any { //    -   Facebook  Google return this.http.get(AbstractAPI.url) .map((res: any) => res.json()) } } 

post.model is a post class in which we wrap naked JSON.

  export class Post { public title: number; public author: string; constructor(post: any) { this.title = post.title; this.author = post.author; } } 

Our application is ready and working, but how can we test all this?

I, in general, a fan of TDD , in this, I first write tests, and then - the code, and for me it is very important to do it as simply and quickly as possible.

I use Karma + Jasmine for tests and the examples will be based on these tools.

Changes that touched all types of tests (models, services, components) - removed {it, describe} from angular / core / testing. Now they are deprecated and reach out of the framework (in my case from Karma).

The loading of standard modules for tests has also changed:
It was:
 import {setBaseTestProviders} from '@angular/core/testing'; import { TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS } from '@angular/platform-browser-dynamic/testing'; setBaseTestProviders( TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS ); 

It became:
 import {TestBed} from '@angular/core/testing'; import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; TestBed.initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); 


Now, for any sneeze, you need to create test @NgModule:
Example with forms:
It was:
 import {disableDeprecatedForms, provideForms} from @angular/forms; bootstrap(App, [ disableDeprecatedForms(), provideForms() ]); 

It became:
 import {DeprecatedFormsModule, FormsModule, ReactiveFormsModule} from @angular/common; @NgModule({ declarations: [MyComponent], imports: [BrowserModule, DeprecatedFormsModule], boostrap: [MyComponent], }) export class MyAppModule{} 


There were a few more changes, but you can read more in the future post from Angular.

Let's start with simple tests:

post.model.spec - everything is simple, we pull the real model and test the properties.

 import {Post} from './../app/models/post.model'; let testPost = {title: 'TestPost', author: 'Admin'} describe('Post', () => { it('checks Post properties', () => { var post = new Post(testPost); expect(post instanceof Post).toBe(true); expect(post.title).toBe("testPost"); expect(post.author).toBe("Admin"); }); }); 

We continue with services where everything is a bit more complicated, but in general the concept has not changed.

post.service.spec - we will write tests for the service that pulls the API:

 import { inject, fakeAsync, TestBed, tick } from '@angular/core/testing'; import {MockBackend} from '@angular/http/testing'; import { Http, ConnectionBackend, BaseRequestOptions, Response, ResponseOptions } from '@angular/http'; import {PostService} from './../app/services/post.service'; describe('PostService', () => { beforeEach(() => { //      TestBed.configureTestingModule({ providers: [ PostService, BaseRequestOptions, MockBackend, { provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) => { return new Http(backend, defaultOptions); }, deps: [MockBackend, BaseRequestOptions]} ], imports: [ HttpModule ] }); }); describe('getPost methods', () => { it('is existing and returning post', //     inject([PostService, MockBackend], fakeAsync((ps: postService, be: MockBackend) => { var res; //     backend.connections.subscribe(c => { expect(c.request.url).toBe(AbstractAPI.url); let response = new ResponseOptions({body: '{"title": "TestPost", "author": "Admin"}'}); c.mockRespond(new Response(response)); }); ps.getPost().subscribe((_post: any) => { res = _post; }); //  ,    tick(); expect(res.title).toBe('TestPost'); expect(res.author).toBe('Admin'); })) ); }); }); 


It remains, in fact, the most difficult thing is to write tests for the component itself. It is this type of test that has affected the greatest changes.

Before explaining in detail what has changed - I would like to create a MockPostService to which I will refer.

post.service.mock - here we will overwrite the actual service methods so that it does not make queries, but simply returns test data.

 import {PostService} from './../app/services/post.service'; import {Observable} from 'rxjs'; export class MockPostService extends PostService { constructor() { //     super(); } //      ,      getPost() { //  Http  Observable,     Observable . return Observable.of({title: 'TestPost', author: 'Admin'}); } } 


Previously, the test for the component looked like this:

 import { inject, addProviders } from '@angular/core/testing'; import {TableComponent} from './../app/components/table/table.component'; //     .          import {TestComponentBuilder} from '@angular/core/testing'; @Component({ selector : 'test-cmp', template : '<table-component [post]="postMock"></table-component>' }) class TestCmpWrapper { public postMock = new Post({'title': 'TestPost', 'author': 'Admin'}); } describe("TableComponent", () => { it('render table', inject([TestComponentBuilder], (tcb) => { return tcb.overrideProviders(TableComponent) .createAsync(TableComponent) //  fixture      .      ,    fixture.debugElement.children. .then((fixture) => { let componentInstance = fixture.componentInstance; let nativeElement = jQuery(fixture.nativeElement); componentInstance.post = new Post({title: 'TestPost', author: 'Admin'}); fixture.detectChanges(); let firstTable = nativeElement.find('table'); expect(firstTable.find('tr td:nth-child(1)').text()).toBe('TestPost'); expect(firstTable.find('tr td:nth-child(2)').text()).toBe('Admin'); }); })); }); 

It became:

 import {Component} from '@angular/core'; // TestComponentBuilder   TestBed,    . import {TestBed, async} from '@angular/core/testing'; import {Post} from './../app/models/post.model'; import {TableComponent} from './../app/components/table/table.component'; // Services import {PostService} from './../app/services/post.service'; import {MockPostService} from './post.service.mock' //        . @Component({ selector : 'test-cmp', template : '<table-component [post]="postMock"></table-component>' }) class TestCmpWrapper { public postMock = new Post({'title': 'TestPost', 'author': 'Admin'}); } describe("TableComponent", () => { //  -    ,      . beforeEach(() => { TestBed.configureTestingModule({ declarations: [ TestCmpWrapper, TableComponent ], providers: [ {provide: PostService, useClass: MockPostService ] }); }); describe('check rendering', () => { it('if component is rendered', async(() => { //   createAsync()  compoleComponents() + createComponent().  -   ,   TestCmpWrapper,  -   .  -  . TestBed.compileComponents().then(() => { let fixture = TestBed.createComponent(TestCmpWrapper); let componentInstance = fixture.componentInstance; let nativeElement = jQuery(fixture.nativeElement); componentInstance.post = new Post({title: 'TestPost', author: 'Admin'}); fixture.detectChanges(); let firstTable = nativeElement.find('table'); expect(firstTable.find('tr td:nth-child(1)').text()).toBe('TestPost'); expect(firstTable.find('tr td:nth-child(2)').text()).toBe('Admin'); }); })); }); }); 


Carefully read the comments in the code itself - there are some minor clarifications.

Comments are welcome and even necessary!

May the Force come with us, because I don’t know what to expect from these guys if they play around in RC so much.

Source: https://habr.com/ru/post/307936/


All Articles