Testing Angular async stuff

Methods one needs to call to make the unit tests work.

I will be using Jasmine under Karma runner, some dependency injection code omitted for brevity.

Jasmine async tests

First, in order for any asynchronous unit test to work under Jasmine, just declare the test function to expect a single argument. This lets Jasmine engine now that your unit test is asynchronous. The argument will be a function your unit test is expected to call once it finishes.

1
2
3
4
5
it('continues after 1 second', function (finished) {
setTimeout(function () {
finished();
}, 1000);
});

You can even shortcut calling finished in most cases

1
2
3
it('continues after 1 second', function (finished) {
setTimeout(finished, 1000);
});

or if using with promise-returning code

1
2
3
4
5
6
7
it('continues after a promise', function (finished) {
service()
.then(function (data) {
expect(data).toEqual(...);
})
.finally(finished);
});

testing $timeout

If you set actions to execute using $timeout service, you can speed things along by flushing

1
2
3
4
5
6
7
8
9
10
11
12
13
var done = false;
function myCode() {
$timeout(function () {
done = true;
}, 1000);
}

it('set done to true', function () {
myCode();
expect(done).toBe(false);
$timeout.flush();
expect(done).toBe(true);
});

testing $httpBackend

Http requests sit in $httpBackend until you flush them

1
2
3
4
5
6
7
8
9
10
11
12
var done = false;
function myCode() {
$http.get('/something').success(function () {
done = true;
});
}
it('gets /something', function () {
myCode();
expect(done).toBe(false);
$httpBackend.flush();
expect(done).toBe(true);
});

testing $broadcast

Scopes listening to events, do NOT require any additional commands to propagate

1
2
3
4
5
6
7
8
9
var done = false;
$scope.$on('myEvent', function () {
done = true;
});
it('sets done on myEvent', function () {
expect(done).toBe(false);
$scope.$broadcast('myEvent');
expect(done).toBe(true);
});

testing promises

Any promise needs a digest cycle (scope $apply call) to actually propagate along the chain

1
2
3
4
5
6
7
8
9
10
11
12
var done = false;
function myCode() {
$q.when().then(function () {
done = true;
});
}
it('sets done on successful resolution', function () {
myCode();
expect(done).toBe(false);
$rootScope.$apply();
expect(done).toBe(true);
});

testing watchers

Any watchers on the scope needs a digest cycle (just like promise) to be updated

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var done = false;
function controller($scope) {
$scope.$watch('foo', function (newValue) {
if (newValue) {
done = true;
}
});
}
it('is done when foo is set', function () {
$scope.foo = 'foo';
expect(done).toBe(false);
$rootScope.$apply();
expect(done).toBe(true);
});

For a good AngularJS testing library that removes a lot of the boilerplate code, check out kensho/ng-describe