"ngImprovedTesting 0.2: adding $q.tick() to improve testing promises"

by Emil — 3 minutes

NOTE: Just released version 0.2.2 of ngImprovedTesting to fix issue #6 causing chained promises (i.e. .then(...).then(...)) not to executed by a $q.tick(); also see README of the GitHub repo.

After quite a while I finally got round to creating version 0.2 of ngImprovedTesting. The ModuleBuilder API is unchanged and still makes mock testing AngularJS code much easier (be sure to read this blog post if you are unfamiliar with ngImprovedTesting).

Version 0.2 of ngImprovedTesting brings you the following interesting improvements:

  • ngModuleIntrospector no longer uses internal AngularJS API.
  • mocks can now also be created manually using the (global) "mockInstance" function.
  • features a more descriptive way of testing promises by adding the tick() method to $q.
  • offers an module called "ngImprovedTesting" to be able to use $q.tick() in your tests without having to use the ModuleBuilder API (which automatically includes the module).

Making testing promises slightly more descriptive using $q.tick()

When testing promises you have to do a $rootScope.$digest() (digest on child scope won't work) in order for .then(...) callbacks (i.e. after a Deferred#resolve(...)) to be actually invoked:

// given
var deferred = $q.defer();
deferred.promise.then(function promiseSuccessCallback() {
  /\* ... \*/;
});

// when
deferred.resolve("someValue");
$rootScope.$digest(); // triggers the promiseSuccessCallback

Having to call $rootScope.$digest() isn't too descriptive and also has some possibly (unwanted) side effects since all scopes will now be $digest-ed (possibly triggering $watch callbacks). After investigating the AngularJS source-code I found out that a $rootScope.$digest() is necessary since $q (indirectly) does a $rootScope.$evalAsync(...) to asynchronously execute callbacks (like the promiseSuccessCallback from our sample). Internally AngularJS even uses a function called "nextTick" that takes a callback as its argument and merely delegates to $rootScope.$evalAsync. Using (the new 0.2 version of) the ngModuleIntrospector we are now able to retrieve the $q provider declaration and alter it:

  • so that a fake $rootScope is injected into the $q provider with (only) a $evalAsync method that no longer invokes $rootScope.$digest() but instead keeps the supplied callback in an internal array.
  • $q instances will get a extra "tick()" method that executes all callbacks from the internal array and then clears the internal array.

So, although a very subtle change, you can now rewrite the "when" part of our previous sample:

// when
deferred.resolve("someValue");
$q.tick(); // triggers the promiseSuccessCallback

The altered $q provided is automatically available when:

  • using the ngImprovedTesting ModuleBuilder in your tests beforeEach(ModuleBuilder.forModule(...)....().build());
  • when using the 'ngImprovedTesting' module in your tests: beforeEach(module(..., 'ngImprovedTesting'));

Enabling $q.tick() support (is disabled at default)

For backwards compatibility the $q.tick() feature is not enabled at default. Instead it should be enabled by setting the $qTick property to true on the (global) ngImprovedTestingConfig object:

beforeEach(ngImprovedTesting.config.$qTickEnable());

Depending on your needs you can either choose to selectively enable $q.tick() (by selectively adding the code above inside a "describe") or alternatively to enable it for all your tests by placing it into a seperate file.

Installing and using ngImprovedTesting

You can easily install ngImprovedTesting through Bower using the following command:

bower install ng-improved-testing --save-dev

To be able to use $q.tick() you either must use the ModuleBuilder API or add 'ngImprovedTesting' to the requires of your module.

meerdivotion

Cases

Blogs

Event