Saturday, October 21, 2017

"Unit testing your Angular Applications" was a tech talk by Keith Stewart at ngHouston.

ngHouston is an Angular meetup group in Houston, Texas which met, the time I attended, at Improving's office. The talk I saw really was a good follow up to a full day workshop I attended ten days earlier at the AngularMix conference on October 9th which was spearheaded by Joe Eames. Keith Stewart works at CollabNet which owns Subversion and therein he is the lead UI developer for DevOps. A unit in a very basic form is one requirement or "the smallest thing you can get in an application" (Keith quote verbatim) and unit testing is distinct from both functional testing (unit tests in the browser) and also end-to-end testing which covers a large swath of functionality in browser-based tests. Protractor is for end-to-end testing though in the Protractor space the end-to-end tests, using the Selenium WebDriver, are confusingly referred to as functional tests in breaking with the definition that most expect. You go from the beginning to the end of a several step process in an end-to-end test. Karma and Protractor are made by the Angular team itself and Wallaby.js is a rival for Karma and Cypress.io a rival for Protractor. Wallaby is something you spin up to run in the background in an Electron shell all of the time you work and it meshes with your IDE (friendly with Visual Studio, Visual Studio Code, WebStorm, and Sublime Text) to put red, white, and green square dots by lines of code. White suggests code that is not covered by test, red a failing test, and green a passing test. Wallaby has metrics for test coverage. karma-coverage-istanbul-reporter was also recommended for test coverage. For all of the tools I have just mentioned, the talk basically focused on Karma/Jasmine tests. A good bit of this talk went over Jasmine matchers such as .toContain() to see if the copy inside of an HTML tag is what is expected in integration tests. .toEqual() and .toBe() are similar .toEqual() and .toBe() are similar with .toEqual() being akin to the == operator and .toBe() mimicking the === operator and matching with type as well as mere value too. .toMatch() is for regular expressions. .toHaveBeenCalled() and .toHaveBeenCalledWith() see if a function has been called and the later affirms certain arguments. In an example of both .toHaveBeenCalledWith() and a spy, Keith offered up this code:

gotoDetail(character: Character): void {
   const link = ['/detail', character.id];
   this.router.navigate(link);
}

 
 

Which was tested with spyon like so:

it('should navigate to the details page for the given character', () => {
   const spy spyOn(router, 'navigate');
   const character = new Character();
   character.id = 1;
   
   component.gotoDetail(character);
   expect(router.navigate).toHaveBeenCalledWith(['/detail', character.id]);
});

 
 

Keith had his own doctored-up version of the Tour of Heroes app around which he wrote some tests. Some code therein that was tested was:

getCharacters() {
   return this.http
      .get(this.charactersUrl)
      .toPromise()
      .then((response) => {
         return response.json().data as Character[];
      })
      .catch(this._handleError);
}

 
 

Alright, above we have an Observable that we just pop off to a promise (returning something once instead having ongoing listening) and then we cast off what we get back. The following test tests as much and it shows off .toEqual(), .toMatch(), and furthermore mocking.

it('should return characters', fakeAsync(() => {
   const character1 = new Character();
   const character2 = new Character();
   let result: Character[];
   
   service.getCharacters().then(characters => result = characters);
   lastConnection.mockRespond(new Response(new ResponseOptions({
      body: { data: [character1, character2] }
   })));
   tick();
   expect(lastConnection.request.url).toMatch(/app\/characters$/);
   expect(result.length).toEqual(2);
   expect(result[0]).toEqual(character1);
   expect(result[1]).toEqual(character2);
}));

 
 

Wow, what is lastConnection and how does it work its magic? Alright, here is all of the code upstream of the test above that is in the same file:

import { fakeAsync, inject, TestBed, tick } from '@angular/core/testing';
import {
   BaseRequestOptions,
   ConnectionBackend,
   Http,
   RequestMethod,
   RequestOptions,
   Response,
   ResponseOptions,
} from '@angular/http';
import { MockBackend, MockConnection } from '@angular/http/testing';
 
import { CharacterService } from './character.service';
import { Character } from './hero';
 
describe('CharacterService', () => {
   let service: CharacterService;
   let backend: MockBackend;
   let lastConnection: MockConnection;
   
   beforeEach(() => {
      TestBed.configureTestingModule({
         providers: [
            CharacterService,
            Http,
            { provide: ConnectionBackend, useClass: MockBackend },
            { provide: RequestOptions, useClass: BaseRequestOptions }
         ]
      });
   });
   
   beforeEach(() => {
      service = TestBed.get(CharacterService);
      backend = TestBed.get(ConnectionBackend);
      backend.connections.subscribe((connection: any) => lastConnection = connection);
   });
   
describe('#getCharacters', () => {
   it('should query the current service url', () => {
      service.getCharacters();
      expect(lastConnection).toBeDefined('no http service connection at all?');
      expect(lastConnection.request.url).toMatch(/app\/characters$/, 'url invalid');
   });

No comments:

Post a Comment