AngularJS Testing using Jasmine


@eitanp461

Developer @HPSoftware

HP Software Blog: http://www.hp.com/go/swdevblog

AngularJS was designed from ground up to be testable




http://angularjs.org/

Agenda



  • Jasmine

  • AngularJS

Jasmine



Why WRITE UNIT TESTS?

Partial list


  •  Verify code correctness
  •  Fast feedback
  •  Assure build quality
  •  Provide documentation for the source code
  •  Uncovering bugs early

UNIT TESTING FRAMEWORKS

Dozens of frameworks







Hello Jasmine


Jasmine is a behavior-driven development framework for testing your JavaScript code.


Jasmine is built in JavaScript and must be included into a JS environment, such as a web page, in order to run .

It does not depend on any other JavaScript frameworks. 


It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.


Maintained by Pivotal Labs on Github

TEST STRUCTURE


describe() {    beforeEach()    afterEach()    it() {        expect().matcher()    }}

SUITES AND SPECS


Suites describe the component under test

Specs describe what each function tests

Nested suites are supported


describe("Distance Converter", function() {
it("converts inches to centimeters", function(){ expect(Convert(12, "in").to("cm")).toEqual(30.48); });})

SUITES and specs


Running selected specs can be done with iit

Disabling selected specs can be done with xit


Running selected suites can be done with ddescribe

Disabling selected specs can be done with xdescribe


iit has higher priority over ddescribe


Don’t forget to fail the build if everything is ignored by mistake (iit, ddescribe)

EXPECTATIONS and matchers


Expectations

Expectations are built with the function expect which takes a value, called the actual. It is chained with a matcher function, which takes the expected value.


Matchers

Each matcher implements a boolean comparison between the actual value and the expected value


a = true;expect(a).toBe(true);

Built-in matchers


Jasmine provides a set of built in matchers :

  • toEqual
  • toMatch
  • toBeTruthy
  • toThrow
  • ...

See fiddle

 

CUSTOM matchers

Write expressive custom matchers to fit your needs.

Add them to your suite using addMatchers .

Matchers can be added globally when added in

a beforeEach function outside the scope of a suite


See fiddle

beforeEach(function() {    this.addMatchers({        toBeLessThan: function(expected) {            return this.actual < expected;        }    });})

spies


Jasmine integrates spies that permit many spying, mocking, and faking behaviors. 


A 'spy' replaces the function it is spying on .


Jasmine spies allow you to test if functions have been called, and to inspect the arguments they were called with. 

spy creation



 

spy usage guidelines


Use spyOn for already existing objects which you need to spy on a specific method, e.g. console.log


Use createSpy for functions called by the code under test with no return value, usually callbacks


Use createSpyObj for interactions of code under test with dependencies, usually other classes


See fiddle

spy training

Now that you’ve created spy functions you can train them to do neat tricks





Asynchronous specs


Jasmine supports running specs that require testing asynchronous operations .


Using the commands waits , waitsFor and runs the test can wait for an async condition to be met and run verifications afterwards

asynchronous specs

waits allows you to pause the spec for a fixed period of time


waitsFor takes a latch function, a failure message, and a timeout.


The latch function polls until it returns true or the timeout expires, whichever comes first. If the timeout expires, the spec fails with the error message.


Once the asynchronous conditions have been met, another runs block defines final test behavior. This is usually expectations based on state after the asynch call returns.

asynchronous specs


waits , waitsFor and runs functions are non-blocking.


The runs function callback is executed only after waits and waitsFor complete successfully


See fiddle

mocking the clock


The Jasmine Mock Clock is available for test suites that need the ability to use setTimeout or setInterval callbacks. 

It makes the timer callbacks synchronous, thus making them easier to test.


See fiddle

MATCHING anything


jasmine.any takes a constructor or “class” name as an expected value.


It returns true if the constructor matches the constructor of the actual value.


See fiddle

exercise

AngularJS Testing





AngularJS


AngularJS was written with testing in mind “so there is no excuse for not testing


This MVW framework's separation of concerns aids with unit testing


On top of that you get  dependency injection which paves the way for using spies

angular mocks


Angular mocks is a collection of test helpers provided by AngularJS team, available on Github


module


The angular.mock.module function  registers  module 
configuration code

It collects the configuration  information which will be used when the injector is created 
by  inject .

The function a llows for mocking out the dependencies of the 
class under test using  $provide

Published  on window for easy  access

inject


The i nject function wraps a function into an injectable function
 
The inject() call creates new instance of  $injector  per test, which is  then used for resolving references

The function c an be called throughout test execution

Once   inject  is called further module registration is  denied


See fiddle

$httpBackend


$ httpBackend provides a f ake HTTP backend implementation 

suitable for unit testing applications that use the $http service

  • Avoid XHR or JSONP requests to the real server
  • Use hard coded responses
  • Control when the response is returned by calling flush


This feature is implemented by Injecting a mock $ httpBackend  implementation  to the $http service


See fiddle

additional helpers


$exceptionHandlerProvider

Configures the mock implementation $exceptionHandler to rethrow or log errors


$logProvider

Mock implementation of $log that gathers all logged messages in arrays (one array per logging level)


additional helpers


$timeout
simple decorator for  $timeout service that  adds a "flush" and " verifyNoPendingTasks " methods

See  fiddle

mock injection

Angular elements such as controllers receive a list of dependencies injected by providers.


Angular modules configuration code must be initialized for tests by calling

angular.mock.module('module-name');


This call collects the configuration information which will be used when the injector is created

It does not initialize the module injectors.


mock injection


After  configuring the module dependencies as above you can  use  $provide  to return stub implementation for selected dependencies


Only on the first call to 

inject(angular-component )

the application "bootstrap" takes place since the html is not loaded and there's no ng-app


mock injection


Declaring substitute implementations for DI with $provide in tests must be done before any inject calls since calling inject initializes all injectors and then it is forbidden to add/define new modules.


Since the real modules are loaded by the browser, when injecting a component it will use the real dependencies. This is not desirable for a unit test since the unit's isolation is broken. You must make sure that mock dependencies are used

promises


The documentation for testing promises is hidden inside the $q  page


The trick is that you have to call scope.$apply () for promise resolution.


See fiddle

filters


Filters are instantiated with Angular’s $filter service


Additionally   filter functions are registered with the $injector under the filter name suffixed with 'Filter '.


Predefined and custom filters can be injected using the syntax  filterName Filter


See fiddle

controllers


Controllers are instantiated with Angular’s $controller service

This allows injecting dependencies during creation.

Since the controller’s interface is its scope it is the main test subject.


See fiddle

services


Services must be injected into the test, there is no $service match for $controller


Injecting service dependencies requires replacing the original values used by Angular’s $inject service with mock ones


Use $provide for registering new providers


See fiddle

service providers


When you want to test the internal functions of a service provider you need to inject it to a configuration block of another Angular test module you create in the test code itself


Note that you still need to inject something for the module “bootstrap”


See fiddle

directives


Tests compile directives using  $compile .


During test execution we compile given DOM structure and use jQuery to test whether the component works as expected.


Note , that DOM structure should not be appended to the document. That makes the tests faster, because browser does not have to render it. So unless it’s necessary ( eg . you need to know some computed property of some DOM element), don’t attach nodes to the document .

directive preprocessing


Directive templates can be inlined in the html or written in an external file which is fetched from the server


In order to avoid fetching an external template you should pre-load the template into  $ templateCache before the test starts


An example implementation is Karma's  html2js preprocessor which  defines a module with the name of the html file and adds it to the  $ templateCache


directives


See fiddle

exercise


test runners


Jasmine is built in JavaScript and must be included into a JS environment, such as a web page, in order to run


Karma is a test runner with built in adapters to commonly used JS unit testing FWs such as Jasmine, QUnit , Mocha and more.

karma features


  • Execute tests on every save by watching source files
  • Smooth Jenkins integration thru grunt plugins singleRun mode
  • Built in JUnit reporter
  • Built in coverage reporter (Istanbul)
  • Allows testing  source files with RequireJS dependencies
  • Runs test suites simultaneously on different browsers, checking code validity on different JS engines

     Get it now!

AngularJS Testing

By Eitan Peer

AngularJS Testing

  • 9,862