Understand Angular Testing With Jasmine & Karma

Daniel Robinson
8 min readAug 14, 2015

This article is a response to the trouble I had finding resources on testing with Karma, Jasmine, and Angular (looking to enable TDD) for someone who is in a learning phase with all three and unit testing in general at the same time.

In this article you won’t find application code; the test code is intended to be clear enough that you can imagine its corresponding application code. Secondly, I expect that you can get a ‘hello world’ environment set up with Karma, Jasmine & Angular, but use or reference this seed project as needed.

Environmental Awareness

When we run Karma (karma run), it loads all the files it’s been told to into the browser. I personally like to configure ‘karma.conf’ like so:

// list of files / patterns to load in the browser
files: [
'app/bower_components/angular/angular.js',
'app/**/*.js'
],
// list of files to exclude
exclude: [
'app/bower_components/**/!(angular.js|angular-route.js|angular-mocks.js).js',
],

This says load angular first, then all other files, but exclude all bower components files, except for the specific ones actually wanted. With this configuration, I can add new files to the project, and they’re automatically picked up. However you do it, the specified files should mimic those served to the browser when the application is run, except for the addition of testing helpers — such as angular-mocks.

It’s important to realise the difference between the browser environment during a karma run vs that when it is being served by an http server. One can investigate by adding the following as the last script in the index.html:

<script>debugger;</script>

Comparing this debug point in the console during a karma run with the same point on a web server we note differences. In the web server case:

describe;
> ReferenceError: describe is not defined
it;
> ReferenceError: it is not defined
expect;
> ReferenceError: expect is not defined
inject;
> ReferenceError: inject is not defined
module;
> ReferenceError: module is not defined

In the karma run case:

describe;
> function jasmineInterface.describe(description, specDefinitions)
it;
> function jasmineInterface.it()
expect;
> function jasmineInterface.expect(actual)
inject;
> function angular.mock.inject()
module;
> function angular.mock.module()

During a karma run context, the window object has been populated with jasmine and angular-mock functions. Angular-mock came from our ‘karma.conf’ file list. Now that we understand these methods are being exposed on the window object, and where they come from, we are fit use them.

Jasmine: ‘it’, ‘describe’, and ‘expect’.

Over on the jasmine docs, they explain that ‘describe’ starts a “test suite”, and ‘it’ starts a “spec suite”. Said another way ‘a grouping of tests’, and the ‘tests themselves’. To start as bare-bones as possible, you can omit the describe, so we can write and execute the most basic suite possible:

it('runs', function(){});

karma run: Executed 1 of 1 SUCCESS

We can make the test fail by causing any standard javascript error:

it('fails', function() {
refErr;
});

karma run: Executed 1 of 1 (1 FAILED) : refError not defined.

We can fail sensible javascript by using the jasmine ‘expect’ and ‘toBe’ functions. (It’s good practice to fail a test first, and then make it pass):

it('has bad expectations', function() {
expect(2 + 3).toBe(6);
});
$ karma run
$ has bad expectations FAILED
Expected 5 to be 6.
Executed 1 of 1(1 FAILED)

Ok, but what about using these functions to make some assertions about the window object? We’ll use describe to semantically state what we’re doing:

describe('window', function() {  it('contains jasmine', function() {
expect(typeof jasmine).toBe(“object”);
});
it('contains angular', function() {
expect(typeof angular).toBe(“object”);
});
it('contains angular mock', function() {
expect(typeof angular.mock).toBe(“object”);
});
});

With just a few functions we’ve written tests to analyse our environment. But how can we test the code inside our angular applications?

Accessing application code from tests

Getting to our angular application code means climbing inside angular’s module system. We’ve seen that angular is available above so we probably could navigate it’s internal API to try to get access inside the modules. However, much simpler, the angular folks have provided us a convenience -the ‘module’ function, and it works hand in hand with the ‘inject’ function. Together they give access to items registered on a module, such as services, factories, constants and values. They’re available on the window object, but don’t take my word for it, here is the proof:

describe('window', function() {  it('has a module function', function() {
expect(typeof module).toBe("function");
}); // passes
it('has an inject function', function() {
expect(typeof inject).toBe("function");
}); // passes
});

Great. Let’s see how to use them:

describe(‘app.constants module’, function() {  it(‘ErrorCodes constant’, function() {

/*
Load the module.
*/
module(‘app.constants’);
/*
Create a function environment which has access to ErrorCodes
by injecting it from the loaded module.
*/
inject(function(ErrorCodes){
expect(ErrorCodes.NOT_FOUND).toBe(‘404’)
});
});});

With these two methods, we have crept inside the module system, and made an assertion about the “ErrorCodes” constant. It didn’t have to be a constant, it could have just as well been a value, service or factory. We are winning. Before pushing ahead, observe that the following three examples achieve the same as above:

Using Jasmine’s beforeEach:

describe(‘app.constants module’, function() {  beforeEach(module(‘app.constants’));  it(‘ErrorCodes constant’, function() {    inject(function(ErrorCodes) {
expect(ErrorCodes.NOT_FOUND).toBe(‘404’)
});
});});

Utilising that inject returns a function:

describe(‘app.constants module’, function() {  beforeEach(module(‘app.constants’));  it(‘ErrorCodes constant’, inject(function(ErrorCodes) {
expect(ErrorCodes.NOT_FOUND).toBe(‘404’);
}));
});

Using inject in the beforeEach:

describe(‘app.constants module’, function() {  var ErrorCodes;  beforeEach(function() {
module(‘app.constants’);
inject(function(_ErrorCodes_) {
ErrorCodes = _ErrorCodes_;
})
});
it(‘ErrorCodes constant’, function() {
expect(ErrorCodes.NOT_FOUND).toBe(‘404’)
});
});

(Here the _underscores_ are a convenience provided by angular to allow us to pull in dependencies almost by name and use the original variable name without running into variable shadowing issues.)

Hopefully through these examples you can observe the relationships between describe, it, beforeEach, expect, toBe, module, & inject. These are our building blocks so far.

Getting At Controllers

Inject gives us access to anything which is part of angular’s provider system. This includes values, constants, factories, services, including all the built-in angular ones like $rootScope. However, controllers are not included, so in a testing context angular makes the $controller helper available to the injector. $controller allows us to instantiate one of our applications controllers. But to test anything meaningful, we’ll also want access to the scope object that our controller will be operating on. This can be done; as follows:

describe(‘HomeCtrl’, function() {  beforeEach(module(‘app.home’));  it(‘initialises scope’, 
inject(function($controller, $rootScope) {
var scope = $rootScope.$new(); var ctrl = $controller(‘HomeCtrl’, {
$scope: scope
});
expect(scope.hello).toBe(“world”) }));});

In fact, we are actually emulating here what Angular does behind the scenes when running the application. A difference is we are storing a reference to the scope object provided to HomeCtrl so that we can test against it.

With inject and $controller, we can get at much of the code we need to. Given that that code often pulls in it’s own dependencies though, perhaps we’re getting more than we bargained for.

Narrowing down to just the code we want to test

So far we know how to access Angular’s containers for application code, but what happens when those containers are pulling in all sorts of dependencies? Currently if we test a factory that depends on four others, angular will be injecting the real versions of the dependencies. What, for example, if those dependencies are making real calls to the outside world, and we’d rather test our factory without them doing so. A common way to do this is mocking: replacing the item under test’s real dependencies with fake ones that are good enough for the purposes of testing. We want to do that in the angular world.

We have already seen that inject will go off and search for items (constants, services, values, etc) registered on angular’s provider system. We have also seen that the module function registers items within a module on that provider system (when we pulled in app.constants, ErrorCodes was then available to the injector). You can think of ‘module’ as taking care of providing, and ‘inject’ as taking care of consuming. But wait, there’s more. Module can also alter what’s provided in another way, using $provide. Let’s investigate:

it('stops for us to analyse $provide', function(){
module(function($provide){
debugger;
});
});

Typing in $provide to the console at this debug point shows us:

constant: (key, value)
decorator: decorator(serviceName, decorFn)
factory: (key, value)
provider: (key, value)
service: (key, value)
value: (key, value)

Much as the methods and their parameter names would imply, $provide allows us to register constants, decorators, factories, providers, services, and values, on the provider system, by specifying a value and a key. In particular, if $provide is used for a key that already exists on the provider system, e.g. because the real one was already loaded, it will be overwritten. Sounds exactly like a way to do mocking.

To illustrate:

describe(‘HomeCtrl’, function() {  // This loads ErrorCodes with ErrorCodes.NOT_FOUND === 404. 
beforeEach(module(‘app.constants’))
// But we'll replace it.
beforeEach(module(function($provide) {
$provide.constant(‘ErrorCodes’, {
NOT_FOUND: ‘has changed’
})
}))
it(‘ErrorCodes constant’, inject(function(ErrorCodes) {
expect(ErrorCodes.NOT_FOUND).toBe(‘has changed’)
}));
});

Of course here we are no longer testing any application code (a danger to watch out for when writing unit tests). Let’s try an example that does:

describe(‘SessionResolver’, function() {  var isAuthenticated;  // Load the real AuthenticationService, SessionResolver
beforeEach(module(‘app.lib’));
beforeEach(module(function($provide) {
// Swap out the real AuthenticationService for our mock.
$provide.factory(‘AuthenticationService’, function() {
return {
isAuthenticated: isAuthenticated
}
});
}));
it(‘Returns false when user is not authenticated’, function(){

isAuthenticated = false;

inject(function(SessionResolver) {
expect(SessionResolver.resolve()).toBe(isAuthenticated)
});
});});

As an exercise to the reader, what would happen if within the same test (same ‘it’ block) we added:

    isAuthenticated = true;    inject(function(AuthenticationService) {
expect(AuthenticationService.isAuthenticated).toBe(false)
});

Would the test pass or fail? What about if it was separated into two tests (i.e. two it blocks)?

And voila, there it is. We are able to swap in mock dependencies just like that using module(function($provide){}). The beautiful simplicity of mocking when using dependency injection.

The Controller Case

Although module(function($provide){}) does work for swapping out a controller’s dependencies, you might note that we already specified our own custom dependency — a $scope. We passed in a map of dependencies although at that time, it just included $scope:

var ctrl = $controller(‘HomeCtrl’, {
$scope: scope
});

But we could have specified any number of dependencies:

var ctrl = $controller(‘HomeCtrl’, {
$scope: scope,
AuthenticationService: function(){
return {
isAuthenticated: true,
};
},
});

This is a convenience alternative when mocking controller dependencies, allowing us to declare and inject mocks all in one place. This may get a bit unwieldy for larger mocks, but module(function($provide){}) is ever at our disposal. It’s another way to swap in controller dependencies in this dependency injection system.

Testing the right amount of code.

We have been playing a paraphrased Goldilocks game. First we were trying to get access to enough application code that we could test something. Then it was making sure we weren’t testing too much. And overall, we’re looking to get it juuuust right. It’s like good science. Isolating and testing the moving parts we care about, while avoiding the effects of those we don’t.

Till’ next time…

In the name of releasing early (and thus more potential for often), I’m going to leave it there, and call this part one. If I get great feedback, I’ll look at going much deeper. Testing things like filters (it’s easy, just inject $filter), directives, and handling promises (you need to $rootScope.$digest() them to make them resolve). Also to take a much deeper look at Jasmine’s spyOn, and other functions to monitor calls to mocks, to improve the power of the assertions in our tests. If you’d like to see more, then please comment, recommend, share, and get in touch (@danztweet)! It’s always more fun with a community of learning :).

--

--

Daniel Robinson

JS, React, Node, Laravel developer. Living in Nelson NZ, working remote for Polygon Research in Montreal.