Testing an Angular Application

OCT 25, 2016 • Written by Catalina De la cuesta

I recently started writing tests for an Angular application, so I had to go through the entire process of researching the tools, installing and setting up the libraries, writing the tests and learning the tricks.

I wanted to do both unit and e2e testing for two reasons:

  1. I needed to be able to test every method independently.
  2. I needed a way to make global tests that would provide a general view of the application and test the UI.

Although there are several tools available, I decided to follow Angular recommendations and write all the tests in Jasmine, run the unit-tests using Karma, and run the e2e tests using Protractor. In this post, I’m going to focus on the unit testing only and discuss the e2e at a later time.

The main documentation resource is the official Angular unit test page.

Set Up

To set up Jasmine and Karma, the best option is to add the dependencies to the package.json file and run npm install. The minimum dependencies are:

karma
karma-jasmine
jasmine-core
Karma-chrome-launcher (supposing you will only run the tests on Chrome)

Apart from the previous packages, that are installed locally to the project, it can be useful to install the CLI globally:

npm install karma-cli -g

After installing the dependencies, run the following command:

karma init

It will ask some questions and will create a ‘karma.conf.js’ file with the default information. You will have to add all the JavaScript files that the browser has to load when your application runs, including libraries. Keep in mind that order is sometimes important, and that you need to load your module before your controllers.

Writing the Tests

Add a new file to the project. It’s recommended that you have one file for each component you’re going to test. It’s also a good idea to name the file after the component. The file name should be saved with the ‘spec.js’ extension.

The file will have some ‘it’ functions inside ‘describe’ function blocks. The tests are actually written in the ‘it’ functions; ‘describe’ gives a way to group them. These functions receive a string and a function. The strings, if chosen wisely, will make the test results more meaningful. For example:

describe(‘Controller AppController’, function() {
    describe(‘ when calling fibonacci(n), function() {
	    it(‘with parameter 1 should return 1’, function() {
		    …
	    };

	    it(‘with parameter 4 should return 5’, function() {
	    	…
	    };

Will output “Controller AppController when calling fibonacci(n) with parameter 1 should return 1”.

There are no strict rules about the use of the ‘describe’ and ‘it’ blocks, so use it to group things that make sense.

To DRY your test, you can use the function ‘beforeEach()’ that can be used to run common setup code for the tests. This function can be used in any ‘describe’ block and will run before each test inside that block. To write common clean up code there is ‘afterEach()’ too.

In the end, tests are about checking expectations. And for that, you use ‘expect’ followed by a matcher function. Some of the most common matchers are ‘toEqual()’, ‘toBeGreaterThan()’, ‘toBeLessThan()’, ‘toBeTruthy()’. You can find a list of all the matchers here.

Example:

expect(whateverImTesting).toEqual(‘whatImExpecting');

Thinking Angular

One of the things I found more challenging was understanding how to test each Angular element, especially how to handle dependencies. The thing is that each one works differently, and actually understanding it implies some understanding of how Angular works.

To begin with, you have to load the module you’re testing. Do it before each test:

beforeEach(module('appModule'));

And from there, depending on what you’re testing, you’ll have to perform some magic. Here’s how to test a controller and a service – all other Angular elements can be tested too, and the ideas are similar.

Testing a controller:

To create a controller, you need the ‘$controller’ method. And to get anything in Angular, you use the ‘$injector’ service (it retrieves object instances). So if you need an instance of ‘$controller’, you just ask ‘$injector’ for it:

$controller = $injector.get('$controller');

And with it, you instantiate your controller like this:

myController = $controller(“ControllerName”, dependencies);

You can do the same with the inject function, which only works for testing, like this:

inject(function($controller){
    myController = $controller(“ControllerName”, dependencies});
});

As you can see, you have to pass the dependencies for the controller. This is where you really get all the power of dependencies for testing. You can pass anything you want! You don’t really have to “depend” on any real thing. Usually, you will mock the dependencies so that you control what they return and can test if they were called and how.

In a simplified way, a mock is just an object that is not the real one. For example, my controller depends on a service that can be very complex:

myRealSevice = {
	iDoSomethingInteresting: function(parameter) { …},
	iDoSomethingCool: function(parameter) {...},
	iAmAmazing: ‘yes I am’
}

And my mock can just be:

myMockService = {}

If I inject this into my controller, at some point, it will likely complain because it will try to call the function ‘iDoSomethingInteresting’ on that object and won’t find it. Easy: let’s add that function to the mock:

myMockService.iDoSomethingInteresting = function() {};

Ok, but if my controller needs something returned by that function? Also easy!

myMockService.iDoSomethingInteresting = function() { return ‘Whatever you need your function to return for your test’}

That’s cool and – as promised – easy. But it can be even cooler and easier! What if you need to test that the controller you’re testing actually did call that function? This is what Jasmine mocks can do. So, instead of just creating any function, I will create a Jasmine spy and that will give me a lot more power:

myMockService.iDoSomethingInteresting = jasmine.createSpy(‘idoSomethingInteresting’)

I can ask the spy to return a specific value:

myMockService.iDoSomethingInteresting = jasmine.createSpy(‘idoSomethingInteresting’).and.returnValue(‘Whatever you need your function to return for your test’)

Later, in my tests, I can ask if it was called and how:

expect(myMockService.iDoSomethingInteresting).toHaveBeenCalled();

Or

                                           .toHaveBeenCalledWith(myParams)
                                           .not.toHaveBeenCalled();
$scope

At some point you will most likely need to pass a ‘$scope’ as a dependency, and although you could pass a simple mock, you might need to create a real scope and pass it:

myScope = $injector.get('$rootScope').$new(); (you are using $injector again to give you an instance of $rootScope and using it to create a new child scope)
Calling controller functions

When you have your controller instance you can call its functions and use its variables. If using the ControllerAs syntax, in your test you will do it like this:

myController.thisControllerFunction(parameters);

And check the expected results:

expect(myController.thisControllerVariable).toEqual(‘the right value’);

Testing a service:

You can instantiate controllers for the tests, manually doing what Angular does every time it finds an ng-controller directive attached to the DOM, but you can’t instantiate services. Unlike controllers, services are singletons that are instantiated by Angular if it finds it is injected somewhere. So for testing, you have to inject the services, but you can’t pass the dependencies as parameters. What you have to do is provide the dependencies to Angular before it instantiates the service so that it uses those objects you provided as the service arguments.

Just as for controllers, you can create mock objects for your dependencies. But instead of passing them to the controller constructor, you provide them before injecting the service:

module(function($provide) {
   $provide.value('myDependency', myMockDependency);
   $provide.constant('myConstant', myMockConstant);
});

inject(function($injector) {
   myService = $injector.get('myService');
});

Keep in mind that all the calls to ‘module(‘someApp’)’ must occur before any call to ‘inject($someDependency)’. That’s why you have to provide the values first in the ‘module’ call and later use the ‘inject()’ or ‘$injector.get’ methods.

(Notice that when you’re providing a constant, you don’t use ‘$provide.value’, but instead ‘$provide.constant’.)

When you’re mocking dependencies you understand another good reason to have small services and controllers that don’t depend on everything!

Some Useful Tricks

$http requests:

It is fairly common to make http requests on your code, and testing it doing real requests can be complicated and unpredictable. Therefore, you better mock it. For this, use ‘$httpBackend’.

$httpBackend = $injector.get('$httpBackend');

You can use “expect” and “when” in your tests:

$httpBackend.when('GET', apiUrl) .respond(200, '');

$httpBackend.expectGET(apiUrl);

To avoid the normal asynchronous nature of the real ‘$httpBackend’, in testing you have to manually flush pending requests.

$httpBackend.flush();

After each test you should verify that there are no requests or expectations pending:

afterEach(function() {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
});

Broadcasting:

If your code uses a broadcast, you can spy it in your tests:

var rootScope;
beforeEach(inject(function($injector) {
    rootScope = $injector.get('$rootScope');
    spyOn(rootScope, '$broadcast');
}));

describe("my tests", function() {
    it("should broadcast something", function() {
        expect(rootScope.$broadcast).toHaveBeenCalledWith('myEvent'); });
    });
});

Running the Tests

You can run the tests from the terminal (‘karma start karma.conf.js’), or add it as a task to your automatic building workflow, gulp file for example. But it’s no secret that I love working with IDEs, and actually one of the reasons is how helpful it is for running tests, so, of course, I also wanted to be able to run the tests from my JS-prefered IDE, WebStorm.

Go to ‘Run->Edit Configurations’, add a Karma configuration and specify the path to the ‘karma.config.js’ file, node interpreter and karma package (ex: ‘/node_modules/karma’). You can also just right-click on the ‘karma.config.js’ file and select Create “karma.conf.js”.

Now you can run and debug your tests. You have to keep in mind that with that configuration you only debug the test code, not the app code that will run on the browser. You can use the development tools on the browser to debug your app.

WebStorm will very nicely display the tests result, with the benefit of being able to click on the test name or error output and being taken to that part of the code.

If you don’t want to run all the tests but just some, add an ‘f’ to the ‘describe’ or ‘it’ function names, so they are ‘fdescribe’ and ‘fit’. Only the tests marked like that will run. If you want to run all but some, add an ‘x’ to the function name.

Run with Coverage

To run the tests with coverage, you will have to install ‘karma-coverage’ (add it to the package.json file) and make some changes to the ‘karma.config.js’ file:

    reporters: ["coverage"],
        preprocessors: {
            "**/*.js": "coverage"
        }

Where you add the source files for which you want to generate coverage.

Reloading Files After Making Changes in the App Code

When running the tests from WebStorm, if you change your app files (for example after building), you have to make Karma reload the files in the browser. This is an ugly workaround, but I haven’t been able to find a clean alternative to stopping and re-running Karma: touch the ‘karma.conf.js’ file after you changed the app code and before running the tests.

Unit Tested

After all of this, you will finally have tested your units! Whew. That was a lot of work, but it’s worth it. You can now be confident that your methods were tested independently and were well covered.

But wait! Unfortunately, there’s more. You may remember me saying something about running e2e tests? It’s that time. Only, you’ll have to wait for my next blog post to find out how I do it.