Testing AngularJS With Karma + Jasmine

In a previous article about Yeoman, AngularJS, and Foundation, I talked about setting up a complete front-end scaffold with Yeoman, AngularJS, and Foundation. Now I want to look at how to test the AngularJS part of your application.

The Tools

Karma

Karma is a test runner for NodeJS. You can use it to simulate multiple devices. What Karma isn’t is a testing framework itself. It is designed to work with various testing frameworks, such as Jasmine, QUnit, or Mocha.

Jasmine

I’m using Jasmine for a couple of reasons:

  • I looked into Jasmine and QUnit before, and I have never seen two tools as similar. Their syntax is different, but how they function is identical last time I checked. Months ago I flipped a coin; it was ‘tails’, and I implemented Jasmine on a client project. No real reason to change.
  • I haven’t looked into Mocha. It might be better, or worse, or also identical. I don’t know. If you have an opinion, let me know.
  • Yeoman installs it by default… sort of.

Step 1: Fix The Missing Dependencies

For some reason, the generator-angular generator comes with some, but not all of the tools we’ll need for running tests with Karma and Jasmine, so we need to fix that before we get started. Run the following lines on the command line:

<br />
npm install grunt-karma --save-dev<br />
npm install karma-phantomjs-launcher --save-dev<br />
npm install karma-jasmine --save-dev<br />

Step 2: Test the Tests

<br />
grunt test<br />

This command should ensure that everything installed correctly. If you are running a clean install from Yeoman, then it should run that test. You can look at the file /test/spec/controllers/MainCtrl.js for an example test.

Step 3: Create A Simple Test

Here is an example test, just to get you comfortable with the Jasmine testing Syntax. Create a file at test/spec/app.js, and write the following code:

<br />
'use strict'</p>
<p>describe('javascript', function() {<br />
  it('should know 2 + 2 is 4', function() {<br />
    expect(2 + 2).toEqual(4);<br />
  });<br />
});<br />

I enjoy jasmine’s matchers and syntax because it reads like English.

describe() Is how you group your tests into suites. You can nest describe()calls as many levels deep as you wish though any more than two and it gets tough to read and manage. it() Is how you run tests. The first argument is the description; the second is the test itself. expect() Is where you test your assumptions in the test, and toEqual() is your matcher. You can also add not() before any more matcher to test the opposite:

<br />
//it's 2014, not 1984 ... I think<br />
it('should know 2 + 2 is not 5', function() {<br />
  expect(2 + 2).not().toEqual(5);<br />
});<br />

If you want to know what other matchers are available to you with Jasmine, here is a handy Jasmine Cheat Sheet.

Step 4: Now For a Real Test

By default, Yeoman comes with a controller test, you can see how it works there:

<br />
describe('Controller: MainCtrl', function () {</p>
<p>  // load the controller's module<br />
  beforeEach(module('ngFoundationApp'));</p>
<p>  var MainCtrl,<br />
  scope;</p>
<p>  // Initialize the controller and a mock scope<br />
  beforeEach(inject(function ($controller, $rootScope) {<br />
    scope = $rootScope.$new();<br />
    MainCtrl = $controller('MainCtrl', {<br />
      $scope: scope<br />
    });<br />
}));</p>
<p>  it('should attach a list of awesomeThings to the scope', function () {<br />
    expect(scope.awesomeThings.length).toBe(3);<br />
  });<br />
});</p>
<p>

Here you see some of the other handy features, such as beforeEach(). beforeEach() Takes a function as an argument, and calls it before each test.

Dependency Injection and Testing

When you start writing tests, you will begin to see why Dependency Injection is so valuable. When you create services in Angular, you want to pass them as arguments instead of creating them directly. This way, you can create mock versions of anything for testing.

Here is an example test from a client project:

<br />
it('should use tasks to set price if that is the invoice_method selected', function() {<br />
  $injector.invoke(function(Project, Feature) {<br />
  var mock = new Project(mock_project()).prepare();<br />
  mock.invoice_method = 'task';<br />
  mock.estimate.tasks = [<br />
    mock_task({'tID' : 1, 'time' : 5, 'rate' : 100, 'billable' : true}),<br />
    mock_task({'tID' : 1, 'time' : 5, 'rate' : 100, 'billable' : true}),<br />
  ];</p>
<p>  mock.updatePrice();<br />
  expect(mock.estimate.price).toEqual(1000);<br />
});<br />

I created functions that would return mock objects such as mock_project() and mock_task(). This way, I can test the Project object without the Task object. This way, all of my tests are completely self-contained, the way good unit tests should be.

Miss your last deadline? Download a Free Chapter of “Dependable: Deliver Software on Time and within Budget”
Click Here to Leave a Comment Below 0 comments

Leave a Reply: