Unit Testing with AngularJS: Testing a Controller

I am going to start by showing you how to test a controllerwhich is simple to do and lets me introduce some of the basic features of the AngularJS mock objects. I added a file called controllerTest.js to the angularjs/tests folder and used it to define the test shown in Listing 25-5.

Listing 25-5. The Contents of the controllerTest.js File

describe(“Controller Test”, function () {

// Arrange

var mockScope = {};

var controller;

beforeEach(angular.mock.module(“exampleApp”));

beforeEach(angular.mock.inject(function ($controller, $rootScope) {

mockScope = $rootScope.$new();

controller = $controller(“defaultCtrl”, {

$scope: mockScope

});

}));

// Act and Assess

it(“Creates variable”, function () {

expect(mockScope.counter).toEqual(0);

})

it(“Increments counter”, function () {

mockScope.incrementCounter();

expect(mockScope.counter).toEqual(1);

});

});

Since this is the first test for AngularJS functionality, I’ll break down each step and explain what’s going on in detail in the sections that follow.

1. Arranging the Test

I need two things to perform this test: an instance of the controller and a scope to pass to its factory function. To reach that point, I have to do some preparation. The first step is to load the module that contains the controller, which I do like this:

beforeEach(angular.mock.module(“exampleApp”));

Only the default AngularJS module is loaded by default, which means you have to call the module method for the modules that you require in your tests, including optional AngularJS modules like ngResource and ngAnimate (which I described in Chapters 21 and 23). I am testing a controller defined in the exampleApp module, and this is the only module that I need to load.

Tip You don’t have to use the angular.mock.module prefix. The methods defined by the angular.mock object are also defined globally, which means you can replace a call to angular.mock.module(“exampleApp”) with just module(“exampleApp”), for example. My preference is to use the longer form because it makes the source of the methods I am calling more obvious.

1.1. Resolving Dependencies

As you have seen throughout this book, dependency injection is an important part of how AngularJS works, and that means unit tests need to be able to resolve dependencies in order to function. The angular.mock.inject method will resolve the dependencies on the function it is passed, and this provides access to the services required for the test, like this:

beforeEach(angular.mock.inject(function ($controller, $rootScope) {

mockScope = $rootScope.$new();

controller = $controller(“defaultCtrl”, {

$scope: mockScope

});

}));

The function I passed to the inject method declares dependencies on the $controller and $rootScope services. In general, the inject method is used to arrange the unit test, and the function that it is passed sets up test variables that will later be used in Jasmine it calls.

My goal in the function shown earlier is to create a new scope and pass it to an instance of the controller in the example application so that it can define its behavior and data. The $rootScope service defines a $new method that creates a new scope, and the $controller service is a function used to instantiate controller objects. The arguments to the $controller service function are the name of the controller (defaultCtrl in this case) and an object whose properties will be used to resolve the dependencies declared by the controller’s factory function. My simple controller requires only a scope for its factory function, but more complex controllers will require other services (which you can obtain through the inject method).

By the time that the function passed to the inject method has completed, the controller will have been instantiated, and its factory function will have operated on the scope I created. I assigned the scope object to a variable called mockScope, which I can then use in the act and assert phases of the test.

1.2. Performing and Evaluating the Tests

The important part of this test is the setup of creating the scope and instantiating the controller. The tests themselves are pretty simple, and I just check that the scope has a property called counter and that calling the incrementCounter behavior correctly changes the value:

it(“Creates variable”, function () {

expect(mockScope.counter).toEqual(0);

})

it(“Increments counter”, function () {

mockScope.incrementCounter();

expect(mockScope.counter).toEqual(1);

});

When you saved the controllerTest.js file, Karma will have run the tests and reported on the outcome, like this:

Chrome 31.0.1650 (Windows): Executed 4 of 4 SUCCESS (25 secs / 17.928 secs)

Four tests are reported because Karma is still finding the firstTest.js file and executing the tests it contains. You can remove this file if you want Karma to report just on AngularJS tests; I won’t be using it again in this chapter.

Tip If you get an error here telling you that some of the tests failed, then you may have ignored my previous warning about removing the examples from previous chapters from the angularjs folder.

Source: Freeman Adam (2014), Pro AngularJS (Expert’s Voice in Web Development), Apress; 1st ed. edition.

Leave a Reply

Your email address will not be published. Required fields are marked *