ANGULARJS AND ANGULAR UNIT TESTING GUIDE WITH JASMINE FRAMEWORK FOR BUSY DEVELOPERS
ANGULARJS AND ANGULAR UNIT TESTING GUIDE WITH JASMINE FRAMEWORK FOR BUSY DEVELOPERS

ANGULARJS AND ANGULAR UNIT TESTING GUIDE WITH JASMINE FRAMEWORK FOR BUSY DEVELOPERS


Unit tests documentations can be overwhelming if you’re a busy developer looking for a way to test a specific functionality or a method. Like most of us developers, unit tests are a drag but we can’t deny their importance in our application's code. I was oblivious to them when I started working and had no idea about them. Step by step, with the help of our seniors and a lot of practice, their implementation became a casual day to day practice whenever a new functionality or a service is added. So, like promised in my blog, I decided to develop a small guide to share with you the necessary techniques when implementing unit tests if you’re a busy developer.

The first thing to know is that Angularjs and Angular put two types of tests at our disposal: unit tests, and End-to-End tests, which are actually functional tests. In this part, we will focus on unit tests.

we will be using Jasmine which is an extensible framework (produced by Pivotal ).

It includes everything needed to:

  • define test suites (describe and it functions),
  • implement assertions of any kind (expect function),
  • quickly implement Spies/mocks (createSpy, createSpyObj and spyOn functions).

The test file structure (describe & beforeEach & it)

You only need to use the following 4 functions to keep your tests structured and well documented:

  • describe: to define a sequence (or group) of "specs".
 describe("list potato products", function() {
  /* code */
 }); 

  • it to define a "spec" (or test).
 describe('list potato products',() => {
  it('should sort potato products by id',() => {});
 }); 

  • expect: to implement assertions. A range of verifiers (matchers) are available to do our verifications. In the following example, toEqual is used.
 describe('list potato products', function() {
  it('should sort potato products by id', function() {
   const potatoIds= [2, 1, 3];
   const sorted = sortUsers(potatoIds);
   expect(sorted).toEqual([1, 2, 3]);
  });
 }); 

  • beforeEach: inside of it we can add general mocks, variable, service injections or spies that will be used in multiple tests inside describe
 describe('list potato products', function() {
  beforeEach(inject(_service1_) => {
   const potatoIds= [2, 1, 3];
  });

  it('should sort potato products by id', function() {
   const sorted = sortUsers(potatoIds);
   expect(sorted).toEqual([1, 2, 3]);
  });
 }); 

Matchers

Examples of matchers with Jasmine:

 expect(booleanValue).toBe(true);
 expect(foo).toBeTruthy();
 expect(foo).toBeFalsy();
 expect(message).toMatch(‘some message’);
 expect(e).toBeLessThan(pi);
 expect(pi).toBeGreaterThan(e);
 expect(a.bar).toBeDefined();
 expect(a.bar).toBeUndefined();
 expect(user.name).toBeNull();
 expect(a).toContain(‘bar’);
 expect(pi).toBeCloseTo(e, 0);
 expect(bar).toThrow();
 expect(function).toHaveBeenCalled(); 

Reverse the condition with .not

.not can be added to reverse the expected result of a test.

 expect(number).not.toEqual(2);
 expect(number). toEqual(0);
 expect(true). toBe(true);
 expect(false).not.toBe(true);
 expect(function).not.toHaveBeenCalled(); 

Test a service

With Angularjs

 describe('list potato products',() => {
  beforeEach(inject(_service1_, _service2_) => {
   const potatoIds= [2, 1, 3];
   //declare services after they have been injected above
   let service1 = _service1_;
   let service2 = _service2_;
  });

  //you can also inject a service directly into the it function
  it('should sort potato products by id', inject((service4) => {
   //access a service’s exported method to test it
   const sorted = service1.sortUsers(potatoIds);
   expect(sorted).toEqual([1, 2, 3]);
  }));
 }); 

With Angular >= 2

use testBed to inject Angular services into unit tests:

 describe('list potato products',() => {
  beforeEach(() => {
   const potatoIds= [2, 1, 3];
   let service = new ValueService();
   service = testBed.get(ValueService);
  });

  it('should sort potato products by id', () => {
   const sorted = service.sortUsers(potatoIds);
   expect(sorted).toEqual([1, 2, 3]);
  });
 }); 

Creating Components

With Angularjs

The $compile service is used to render the directive.

 describe('list potato products',() => {
  beforeEach((_$compile_, _$scope_) => {
   let $compile = _$compile_;
  });

  it('should sort potato products by id', function() {
   // Compile a piece of HTML containing the directive
   const element = $compile("/**/")($scope);
    $scope.$apply();
   expect(element.html()).toContain(‘some element’);
  });
 }); 

With Angular >= 2

The TestBed.createComponent method instantiates the component whose type is passed as a parameter and then returns a ComponentFixture object to control and inspect the component.

The main properties and methods of this class are:

  • componentInstance: the instance of the BookPreviewComponent class,
  • debugElement: object for inspecting and handling the DOM.
  • detectChanges:triggers the Change Detection.
 import ‘PotatoComponent’ from ‘./path’;
 describe('list potato products',() => {
  beforeEach((_$compile_, _$scope_) => {
   const fixture = TestBed.createComponent(PotatoComponent);
   const component = fixture.componentInstance;
   const debugElement = fixture.debugElement;
  });

  it('should sort potato products by id', function() {
   component.potato = new Potato({
    id: 'potatos 1'
   });
   fixture.detectChanges();
   expect(debugElement.nativeElement.querySelector('p').textContent).toEqual('potatos 1');
  });
 }); 

create a controller (Angularjs only)

It’s a good practice to declare the controller in beforeEach if you intend to use it in multiple tests to avoid duplications.

 describe('list potato products',() => {
  beforeEach((_$controller_) => {
   $scope = {};
   //declare the controller in before each or in the it clause
   $controller = _$controller_;
   controller = $controller('PotatosController', { $scope: $scope });
   });

  it('should sort potato products by id',() => {
   const potatoIds= [2, 1, 3];
   //use the $scope provided by the declared controller
   $scope.sortUsers(potatoIds);
   expect($scope.potatosList).toEqual([1, 2, 3]);
  });
 }); 

Mock methods

The spyOn method allows to track a service’s function behaviour like the value it returns, arguments…

 const spiedMethod = spyOn(potatoService, 'getPotatosList'); 
 ctrl.initComponent(); //initialise a component
 expect(spiedMethod).toHaveBeenCalled();
 expect(spiedMethod).toHaveBeenCalledWith(‘argument1’, ‘argument2’); 

Using the spyOn function by jasmine also can make any method return the value you want. Therefore you don’t need to worry about the inner working of a function and just force it to return whatever value to continue working on the test.

 spyOn(potatoService, 'getPotatosList').and.returnValue(result); 

CallThrough is used to spyOn a function and still call it

 spyOn(potatoService, 'getPotatosList').and.callThrough(); 

Unit tests with Asynchronous methods

With Angularjs

You can spy on an asynchronous function and make it return a specific object.

 it('should display potatos list when selecting a project',inject( ($q, potatosApi) => {
  $controller('PotatosController', {$scope});
  spyOn(potatosApi, 'getPotatosList').and.returnValue($q.when({
   potatos: [
    {id: 1, name: ‘potato 1’},
    {id: 2, name: ‘potato 2’}
   ]
   }));
   $scope.selectPotato({id: '1'});
   $rootScope.$digest();
  expect($scope.selectedPotato.name).toEqual(‘potato 1’);
 })); 

In the example below we want to test the result returned by getPotatoStatus.

 it('should check the potato’s status', inject((potatosApi) => {
  const potato=  {id: 1, name: ‘potato 1’};
  potatosApi.getPotatoStatus(potato).then((result) => {
   expect(result.status).toEqual(‘Raw potato’);
   expect(result.label).toEqual(potato.name);
  });
  $rootScope.$digest();
 })); 

With Angular >= 2

The async utility tells Angular to run the code in a dedicated test zone that intercepts promises. The use of async with whenStable allows us to wait until all promises have been resolved to run our expectations.

 it('should check the potato’s status', async(inject((potatosApi) => {
  const potato=  {id: 1, name: ‘potato 1’};
  potatosApi.getPotatoStatus(potato).whenStable().then((result) => {
   expect(result.status).toEqual(‘Raw potato’);
   expect(result.label).toEqual(potato.name);
  });
 }))); 

References

Angular unit tests official documentation

Angularjs unit tests official documentation

To view or add a comment, sign in

More articles by Samer Cherif

Others also viewed

Explore content categories