Angular Directive Unit Test Template for WebStorm

If you’ve seen any of my Angular code, you will notice that I use a LOT of element directives (Angular 2 calls these “components”!). You’ll also notice that I like writing a lot of unit tests.

After my previous post on WebStorm templates, I came to the realization that I was writing a lot of boiler plate code for testing these directives. Instead of rewriting these tests every time, I’ve created a template to use for unit testing directives.

(One thing to note, this requires at least angular mocks 1.3.15 for the bindings options in the $controller constructor)


'use strict';
describe('My Great Directive', function() {
var $rootScope;
var $controller;
var $window;
var $httpBackend;
var $compile;
var scope;
var MyFancyService
var MyGreatDirectiveController;
beforeEach(function() {
module('myApp.greatDirective');
inject(function(_$rootScope_, _$httpBackend_, _$compile_, _$controller_, _$window_, _MyFancyService_) {
// Native Angular Injections
$rootScope = _$rootScope_;
$controller = _$controller_;
$window = _$window_;
$compile = _$compile_;
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
// Custom Injections
MyFancyService = _MyFancyService_;
// Locals are the injections to the directive's controller
var locals = {
$window: $window,
MyFancyService: MyFancyService
};
// Bindings are the objects that are bound to the directive's scope
var bindings = {
fancyString: 'fancy-string'
};
MyGreatDirectiveController = $controller(
'MyGreatDirectiveController',
locals,
bindings
);
});
});
describe('Directive Controller', function() {
it('should be a proper contoller', function() {
expect(MyGreatDirectiveController).toBeDefined();
});
});
describe('Compiled Directive', function() {
var responseText;
beforeEach(function() {
responseText = 'My Great Directive!';
$httpBackend.whenGET('/app/partials/great.directive.html').respond(responseText);
});
it('should compile', function() {
var element = $compile('<my-great-directive></my-great-directive>')(scope);
scope.$digest();
$httpBackend.flush();
expect(element.html()).toContain(responseText);
});
});
});

Angular Project Blackjack: 11 – User Experience

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

Foreward

The coding portion of this post was BY FAR the most amount of time and effort I’ve put into any of them so far. My CSS skills were very rusty, so I had to do a lot of research and back and forth to get things right. More advanced front-end people may find some issues with my code, but it is, as always, a work in progress.

Experience

While our blackjack game is technically correct, it has all the glimmer of a game written for the TI-82. Don’t get me wrong, I love bootstrap, but for a game, it just isn’t enough.

Read More »

Angular Project Blackjack: 9 – Game Service

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

Back to it

We’ve got a good setup now, with our gulp file building and running as necessary. Let’s move back to working on our code!

A Game Service

In our GameController we have the ability to deal a hand of cards but we can’t really do anything with it yet. To do so, we need to start putting the blackjack game rules into our application. Let’s create a GameService to start handling these rules for us. The main thing our GameService will do for now will be to take a hand of cards and return the numeric value of that hand. We can then use that value in our controller to enable/disable actions a user can take on the hand.

Read More »

Angular Project Blackjack: 6 – Do You Even TDD Bro?

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

Previously, I had mentioned that test driven development hasn’t really “stuck” for me and I find myself switching back to BDD for the most part. I find that this is usually because I don’t plan features out far enough in advance and I’m more of the “experiment and refactor” kind of developer. I have found instances where TDD actually works better for me, and today’s topic is one of those instances.

We are going to be working on the card service. This service will allow us to get a deck of cards, shuffle the deck, deal from the deck. Since this blackjack project is a game based off of set rules, we know how the cards should behave. We can write tests against these rules and then we’ll write our service to meet the rules. The rules are:

  • We should be able to get a new deck of cards with the object type “Deck”
  • The deck should contain 52 cards
  • There should be no duplicate cards in a deck
  • Each card should have a rank and a suit.
  • We should be able to ‘deal’ a card from a deck that has undealt cards in it.
  • Attempting to ‘deal’ from a deck with no undealt cards returns false
  • When a card is dealt, it is no longer in the cards array
  • We should be able to shuffle the deck and randomize all undealt cards.
  • We should be able to ‘reset’ a deck that will move all dealt cards into the undealt status and shuffle.

Now let’s write those tests:

Read More »

Angular Project Blackjack: 3 – Unit Testing

(This post is part of my “from scratch” AngularJS project. If you are feeling lost, the first post is here.)

Now that we have our application running and our first controller done, the next thing we want to get setup is our testing framework. Having a testing framework ready to go is always beneficial, no matter what kind of development style you choose. I haven’t been able to get on board with TDD, but that is just my personal preference. I do like to have unit tests that cover my code, but I find it easier to write the tests after the code rather than before.

We are going to use the karma test runner suite to run our jasmine tests. Karma is a tool developed by the AngularJS team to run unit tests against a browser. It has the option to watch files for changes and re-run tests as soon as it detects them. It also reports passes/failures of the tests. Jasmine is the tool that we will write our tests in. We will test each part of the code, expecting certain things to happen, causing the tests to pass or fail.

Let’s get our environment setup to do some testing. Since this is first time we’ll be using npm to install something we want to save to the project, let’s first create a package.json file in the projects root directory:

{
  "name": "angular-blackjack",
 "version": "0.0.1"
}

Now we are able to install packages with npm and save them to our package.json file so that they can be installed on any other machine that we take the project to.

npm install karma --save-dev
npm install karma-phantomjs-launcher --save-dev
npm install jasmine-core --save-dev
npm install karma-jasmine --save-dev
bower install angular-mocks --save-dev
karma init karma.config.js

This will initialize our karma test runner file karma.config.js. When it asks for which browser you would like to test on, enter ‘PhantomJS’. This is our “headless” browser that runs nicely on the command line. The most important part right now is to make sure karma is loading all of our source files properly like so:

files: [
'./src/bower_components/angular/angular.min.js',
'./src/bower_components/angular-mocks/angular-mocks.js',
'./src/app/blackjack.module.js',
'./src/app/**/*.module.js',
'./src/app/**/*.js',
//Test Specs
'./src/app/**/*.spec.js'
] 

We are telling karma to load all of our javascript files in the src directory, but we want to specifically tell it to load the ‘.spec.js’ files. These will be our tests.

In order to execute tests, inject angular code and core services, we also install angular-mocks. From the documentation, angular-mocks (or ngMocks) “provides support to inject and mock Angular services into unit tests”. This means we do things like “inject”, “dump”, and “module”. Things that are necessary to test our code.

Following the style guide, we want to keep our tests along side of our source code and name the tests as close to the files they are testing as possible. Our first tests will be on our game.controller.js file, so we will put it in the same directory and call it game.controller.spec.js. Let’s go through our test:

describe('GameController Unit Tests', function () {
    var gameController;
    beforeEach(function () {
        module('blackjack.game');
        inject(function ($controller) {
            gameController = $controller('GameController');
        });
    });

    it('should be true', function () {
       expect(true).toBe(true);
    });

    it('should have a start and end function', function () {
        expect(gameController.start).toBeDefined();
        expect(typeof gameController.start).toBe('function');
        expect(gameController.end).toBeDefined();
        expect(typeof gameController.end).toBe('function');
    });
});

First, our describe() function, well, describes the tests that are run in the second parameter. Everything within beforeEach() function will be executed, I hate to say this, before each test is run. Notice a pattern here? The Jasmine framework makes very readable code. Next, we’ll load our blackjack.game module with the module() function. The inject() function is where things get a bit different. Since we will be creating a controller to test, we will use the $controller service. We’ll create our ‘GameController’ within the inject function, so we have access to the $controller service we just injected. If we were to need the $controller service outside of the inject function, there is another method we could use, but that is (yet another) blog post!

Now that our gameController variable has been defined, we can run some actual tests on it. The first test:

expect(true).toBe(true);

is a good way to reveal any setup issues you may have. If the tests fail, you know you’ve done something wrong. For the next test, we will actually verify that our controller has the ‘start’ and ‘end’ functions available:

it('should have a start and end function', function () {
    expect(gameController.start).toBeDefined();
    expect(typeof gameController.start).toBe('function');
    expect(gameController.end).toBeDefined();
    expect(typeof gameController.end).toBe('function');
});

These ‘toBeDefined’ functions verify that, yes, these functions are defined on the controller. But, if there was a property called ‘start’, this would validate as true also. That is why we have the ‘typeof’ validation as well.

These tests can be run on the command line with

karma start karma.conf.js

We now have our unit testing framework setup and running a test against our first controller that we wrote. Not a bad start, especially without using any sort of generator or starter template!

One final note. I switched over to WebStorm a few months ago and I couldn’t be happier with the IDE. Coming from a Visual Studio and an XCode background, this IDE makes me feel like home. Here is WebStorm’s visualization of the tests we just wrote, pretty great right!?!

Screenshot 2015-02-11 22.23.42

Here’s the results of our work!

Up next: Making A Directive

AngularJS: Testing a Directive’s Controller with Isolate Scope Expressions

When writing unit tests for an AngularJS directive, there’s not a breadth of information online past the ‘basics’. This fact came to me rather quickly when I tried to write a simple unit test titled: “it(‘should call the passed function’…”. I had created a directive which accepted an isolate scope expression passed from the parent controller. Side-note: a good recap on isolate scope is found here. I finally figured it out with a little ‘controllerAs’ magic.

To follow along, open this jsfiddle!

“How did this work and what was I testing?”


myApp.controller('MyDirectiveController', ['$scope',function($scope){
self.doSomething = function (){
//Call the passed expression
$scope.passedExpression();
};
}]);

This is the entire directive’s controller. My function in the directive’s controller (‘doSomething’) calls the passed expression (‘passedExpression’) from the parent controller. What I was trying to do in my test was verify that when a method in the directive’s controller was called, it was actually calling the function on the parent controller.

The Directive


myApp.directive('myDrtv', function () {
return {
restrict: 'E',
scope: {
passedVar: '=',
passedExpression: '&amp;'
},
template: '
<div>Hello {{passedVar}}</div>
',
controller: 'MyDirectiveController',
controllerAs: 'myDirectiveCtrl',
replace: false
};
});

view raw

myDrtv.js

hosted with ❤ by GitHub

So, our directive accepts the passed expression and it gets assigned to the directive’s isolate scope as  “$scope.passedExpression”. My initial thought was to simply test the MyDirectiveController without actually compiling the directive, but that doesn’t give us a full test (and would be way too easy to do!).

“Show Us the Test!”


describe('myApp', function () {
var element, scope, innerScope, elementCtrl;
beforeEach(function () {
module('myApp');
//Create Element with our directive
element = angular.element('<my-drtv passed-var="passThis" passed-expression="myFunction()">');
inject(function ($rootScope, $compile) {
scope = $rootScope.$new();
//Create scope variables to pass to the directive
scope.passThis = 'Passing';
scope.myFunction = function(){};
$compile(element)(scope);
scope.$digest();
//Now our element is ready and behaving like it would on a page
innerScope = element.isolateScope();
elementCtrl = innerScope.myDirectiveCtrl;
});
});
it('says hello', function () {
expect(element.text()).toBe('Hello Passing');
});
it('should call the passed function', function(){
//Watch our main scope's function
spyOn(scope,"myFunction");
expect(scope.myFunction).not.toHaveBeenCalled();
//Tell the element to call it's function that calls the parent's function
elementCtrl.doSomething();
expect(scope.myFunction).toHaveBeenCalled();
});
});

view raw

test.js

hosted with ❤ by GitHub

The first test verifies that our parent scope’s variable was passed to the directive properly. The next one is what caught me. I could not figure out how to call the “doSomething()” function on the element’s controller. I tried using the ‘$controller’ injection to get to it, but the instance it created was not the same as the element’s controller. Once I started debugging, I started looking at the results of the function “element.isolateScope()“. There was a property on that scope object that was returned “myDirectiveCtrl” that I immediately found out was the directive’s controller instance! After that I could call whatever I wanted on it. With jasmine, I’m spying on my parent scope’s function to make sure it is being called.

I did see that there was an “element.controller()” function, but it never worked properly for me. If it works for you, please let me know!

“Now what?”
Go write some better unit tests! Also, write as many directives as you can! I’ve found that moving code out of my templates and into directives has really helped modularize my projects. Being able to reuse a directive is such a time (and code) saver!

Once again, this example lives on jsfiddle.