Let’s Play MEAN, Part V

Building a starter repo from scratch


Welcome back to this exploratory dive into the MEAN stack.
We’re building a seed repo we can use for all future MEAN projects.
This is Part V —Don’t get ahead of yourself, start with Part I.

Front-end testing.

Back in episode III, we looked at back-end testing with Node. Today we’ll be testing its front-end counterpart!

Tools of the Trade

I’m going to stick with Mocha/Chai for test running and assertions, and I’ll be using Karma to emulate our server for unit tests. I’ll also be using Protractor for end-to-end tests.

Unit Testing with Karma

Karma is a Node module, so let’s start by installing its command-line client:

$ sudo npm install -g karma-cli

Next, let’s add Karma and Karma-related accessories to our project:

$ sudo npm install --save-dev karma karma-mocha karma-chai karma-phantomjs-launcher karma-chrome-launcher
“—save-dev” is a convenient little shortcut I don’t think we’ve talked about. It installs and automatically adds the plugins to our devDependencies in package.json. It even sorts them alphabetically, which is a pretty cute touch.

The first thing we need to do is create our karma configuration file. Not satisfied to let you open your boring text editor and fill it in manually, the cool cats at Karma created a yeoman-style wizard to guide you through.

$ sudo karma init

I chose the options listed above, and was delighted to see that it did indeed generate a config file. Don’t worry about those WARNings, we’ll create some files for it to match soon enough.

**/*.e2e.js refers to End To End tests, which we’ll get to a little later on. They run with Protractor, not Karma.

One quick change we need to make to karma.conf.js:

frameworks: [‘mocha’, ‘chai’],      // Add 'chai' to this array
Credit where credit is due
As I write this section, I’m by-and-large just paraphrasing this excellent blog post from Ben Drucker. He goes into more depth and is a way more authoritative source for this stuff, so you might be better off hopping over there and coming back for the end-to-end stuff below.

MOAR ANGULAR.

Angular has a lot of additional, official content packaged separately. I want to add two more of these packages to our project: ngResource, for making HTTP queries, and ngMock, for faking HTTP queries. Add this to your bower.json:

“dependencies”: {
“angular”: “~1.3.0",
"angular-resource": "~1.3.0",
“angular-route”: “~1.3.0",
“angular-mocks”: “~1.3.0"
}

And then, to install it:

$ bower install

Now, we need to make sure Karma is aware about all of our javascripts, including Angular, its addons, and our modules. Add this to your karma.conf.js:

files: [
'public/assets/libs/angular/angular.js',
'public/assets/libs/angular-mocks/angular-mocks.js',
'public/assets/libs/angular-resource/angular-resource.js',
'public/assets/libs/angular-route/angular-route.js',
'public/**/*.module.js',
'public/app.routes.js',
'public/app.js',
'public/components/**/*.js',
'public/shared/**/*.js'
'test/client/**/*.spec.js'
],

The order matters!

Phew, we made it through the config. Let’s test something!

Thing Service

In a real app, we’d have a Thing service that would fetch stuff from our JSON API, for Index and Show actions. We already wrote the code to return a JSON array of all of our Things back in Part II, and we tested it in Part III.

Because we already know that the back-end works, we can just mock that response and test against a pre-saved object. We’ll also be fancy and do this TDD style; tests before code.

If you haven’t read the $resource docs, I recommend it; it’s a powerful little module. But the cliff notes version is it takes a URL and returns an object with a set of CRUD-ish actions for that URL:

{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};

The one we care about is ‘query’; it’s essentially Index. So, here’s what my test looks like so far:

// test/client/thing/thing.service.spec.js
describe(“Thing service”, function() {
var thingService, httpBackend, mockResponse;
  beforeEach(module(“mainApp”));
  beforeEach(inject(function(_thingService_, $httpBackend) {
thingService = _thingService_;
httpBackend = $httpBackend;
mockResponse = [
{ _id: ‘5483182e400235e24d0b819c’, name: ‘Table’, __v: 0 }
];
}));
  it(‘GET :INDEX’, function () {
httpBackend
.expect(‘GET’, ‘/api/things’)
.respond(200, mockResponse);
things = thingService.query();
console.log(things);
httpBackend.flush();
console.log(things);
expect(things[0] instanceof thingService).to.equal(true);
expect(things[0].name).to.equal(mockResponse[0].name);
});
});

There are a lot of finicky details here, which I suppose is the inevitable consequence of an asynchronous language being tested synchronously.

$httpBackend
We’re using ngMock’s $httpBackend. Essentially what it does is it waits for a request on the expected path (/api/things, in this case). When it receives one, it responds with a 200 OK status code and a predefined response.

That response, by the way, is copied straight from Part III. It’s exactly what the server spits out during our back-end tests.

The thing that makes this tricky is that $httpBackend — the real version — is asynchronous. It just sits and waits and responds at its leisure. Thankfully, because Angular devs are nice, they found a way to keep the integrity of the service without turning unit tests into callback hell: flush().

$httpBackend.flush() will terminate the async waiting period and fulfill whatever requests we have left. As grotesque as the metaphor is, I like to think of it as a toilet. The toilet sits waiting for whatever your body ejects into it, and only when you flush does it do its disposal. The toilet doesn’t know when to flush, it just sits and waits.

There’s dependencies, and there’s _dependencies_.
This weird underscore format is to handle injection without namespacing issues. We have a thingService, and we want to assign it to a variable named thingService. Somehow, I don’t think this would work:

function(thingService) {
thingService = thingService;
}

So, to make our lives easier, Angular will recognize that _whatever_ should look for a provider named whatever. It’s just a convenience.

Let’s hit save, and Karma will give it a shot:

Oh dear…

We get two error messages, one for each browser it’s testing in. That makes sense, right? We never actually created our service, we’re just making reference to it.

Let’s create the minimum needed to get past that error message.

angular.module(‘mainApp.things’)
.service(“thingService”, function() {});
we’re making progress!

The reason it’s failing now is that $httpBackend is waiting for a request, and we never send one. Time to inject our friend $resource and throw a request in that direction:

angular.module(‘mainApp.things’)
.service(“thingService”, [‘$resource’, function($resource) {
return $resource(‘/api/things’);
}]);
Woohoo!

Cool! I totally didn’t expect it to be that easy.

Also, check out our logs. Before we flush() the request, it’s stored as an empty array. The flush pushes that stored response into the service, and the service populates with it. I know we’ve basically built the simplest possible HTTP service, but I still get a little kick over how cool it is. Plus seeing big green SUCCESS messages always brightens my day.

ThingController

So we already created our ThingController, but it’s pretty damn sparse. As a reminder, here it is:

function ThingController($scope) {
this.tagline = “I’m such a Thing.”;
}

ThingController.prototype.someMethod = function() {};

The service we just created should really be injected into it; We’ll want to get a list of all the Things and assign it to our controller! But for now, let’s write a quick spec to test what we have:

describe(“PersonController”, function() {
var controller, scope;
  beforeEach(module(‘mainApp’));
  beforeEach(inject(function($controller, $rootScope) {
controller = $controller(‘ThingController’, {
$scope: scope
});
}));
  it(‘assigns a tagline to the controller’, function() {
expect(controller.tagline).to.not.be.undefined;
});
});

A lot of this should look pretty familiar, but there’s one new tricky bit: $controller.

Digging Deep: $controller

What follows is a detour into the lower levels of Angular. I find it interesting, but it’s not really necessary information; feel free to skip it, and don’t worry if you aren’t following it.

A lot of what Angular does happens behind-the-scenes. $controller is a service that instantiates a controller registered with $controllerProvider. Let’s explore.

After taking a look at the source, I believe it works like this: When you create a new controller with module.controller(“WhateverController”), that controller gets registered with $controllerProvider but it doesn’t get instantiated. The instantiation comes when you connect it to the DOM, either with the ng-controller directive or as part of your ngRoutes (like we’re doing).

$controllerProvider.controllers is a hash (or an object, whatever) where the key is the name of the controller, and the value is the constructor function. For example, in our current project, this is what it holds (I think):

{
"ThingController": function($scope) { this.tagline="stuff" }
}

When we boot up our test suite, we need to grab that reference to the constructor function and instantiate it. So, we use the $controller service to help us out, which scans that object for the key we pass in, and invokes its value.

The only problem, though, is that the controller has locals: The dependencies we pass in. In our example, we only have one dependency: $scope. If we try invoking it without passing in a scope object, we wind up with this error:

Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope

The quick fix is to just create a new scope and pass that in, so that’s exactly what we do!


We’ve successfully unit-tested a service and a controller. We’re skipping directives, the third part of a unit-testing trilogy, because I can’t think of a generic enough directive to include; remember, this is a seed project, not a real app, and so I only want components that are usable in every project.

Onwards!

End-to-End Testing with Protractor

Made by the Angular team, Protractor is a pretty ideal tool for end-to-end testing, which is the cool kind of testing where a real browser opens up and a ghost types into forms and clicks buttons. At my last job, these types of tests were a great way to run through a complex campaign-creation process, and for that alone I’ll be eternally grateful.

Installation and Setup

Let’s install it globally, so we have access to command-line tools:

$ sudo npm install -g protractor

Protractor comes bundled with a second tool, webdriver-manager. Webdriver-manager is a tool that starts up a Selenium server, and this is responsible for that ghost-at-the-keyboard magic where forms fill themselves out.

Let’s make sure that it’s up-to-date, and then let’s start it:

$ sudo webdriver-manager update
$ webdriver-manager start

The first time I started it, I got this error popup:

Head on over to their download site and grab it. Run ‘webdriver-manager start’ again, and we should be good to go.

We need a configuration file, the Protractor version of node’s server.js. Here’s mine:

// protractor.conf.js

exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['test/client/**/*.e2e.js']
}

seleniumAddress is where our freshly-installed Selenium server lives. I’ve gone with the default value, no additional setup necessary.

specs are the tests we want this file to run, and this naming convention is important. I’ve ended unit tests with .spec.js, for Karma to detect and run, and end-to-end tests with .e2e.js, for Protractor. That way, these two systems co-exist amicably, with neither of them encroaching on the other’s turf.

Here’s how we get Protractor to run:

$ protractor protractor.conf.js

If you run this now, you’ll get an the following error: Spec patterns did not match any files. Worry not, we’ll create some shortly.

Our First Test

Let’s do this:

// test/client/thing/things.e2e.js

describe('Things', function() {
it('should be linked to from the root page', function() {
browser.get('http://localhost:3000');
// hmm...
});
});

So I got this far, and then stopped. Let’s quickly go over what we’ve got though.

Protractor runs with the Jasmine library, whose syntax is annoyingly similar to Mocha/Chai. It’s annoying because there are small differences, and they will trip me up, but I suppose overall it’s small potatoes.

browser is provided by Protractor, and it’s super handy. I console.logged it, and here’s a small selection of the stuff it provides:

{ getSession: [Function],
getCapabilities: [Function],
actions: [Function],
executeScript: [Function],
executeAsyncScript: [Function],

call: [Function],
wait: [Function],
sleep: [Function],
getWindowHandle: [Function],
getAllWindowHandles: [Function],
getPageSource: [Function],
close: [Function],
getCurrentUrl: [Function],
getTitle: [Function],
findElementInternal_: [Function],
findDomElement_: [Function],
findElementsInternal_: [Function],
takeScreenshot: [Function],
manage: [Function],
params: {},
}

I’ve bolded the ones I find particularly interesting. Apparently it has a built-in way to take screenshots; that’s awesome.

But anyway, back to our test. I’ve made a request for the browser to get our index page, and then I paused. Let’s take a look at what our index.html looks like.

<div class=”container”>

<!-- HEADER -->
<header>
<a href=”/”>Mean Stack Template</a>
</header>
<nav>
<!-- LINK TO OUR PAGES. ANGULAR HANDLES THE ROUTING HERE -->
<ul>
<li><a href=”/things”>Things</a></li>
</ul>
</nav>

<!-- ANGULAR DYNAMIC CONTENT -->
<div ng-view></div>
</div>

Ok, cool. We have our nav links at the top, and we want to click that ‘Things’ link. Let’s figure out what we want this test to do in pseudo-code.

find link with text "Things"
click link

Expect page URL to be /things
Expect page to have a tagline
Expect page's tagline to equal whatever we set it to in the controller.

So the first thing we need is a locator. Thankfully, Protractor has a bunch: Both ones specific to Angular, and default ones that come with WebDriver.

Next, we need our expectations. In unit testing, it’s considered good form to have one expectation per test. In end-to-end, that just doesn’t make sense; you want to test a whole process, making expectations along the way.

Here’s what I came up with:

// test/client/thing/things.e2e.js

describe(‘Things’, function() {
it(‘should contain our tagline’, function() {
browser.get(‘http://localhost:3000');
element(by.linkText(“Things”)).click();

expect(browser.getCurrentUrl())
.toEqual('http://localhost:3000/things');
expect(element(by.binding(“thing.tagline”)).getText())
.toEqual(“I’m such a Thing.”);
});
});
Nothin’ but green, all day long.

Pretty straightforward, right? We’re using WebDriver’s linkText selector to find that Things link, clicking it, and then using Protractor’s binding selector to find the associated tagline. We’re also using browser’s getCurrentUrl() to make sure the link brought us to the right place.

Additional Reading
End-to-end testing is really powerful, but there’s only so much we can test with this shell of a project. I’ll stop here for now, but I encourage you to check out their official tutorial, as well as the API docs.

Woohoo, we’re almost done!

We’ve got our front-end and back-end functioning and minimally tested. What could possibly be left?

Next week we’ll cover task running with Gulp. This is an incredibly important step, and it’ll make our development lives much less tedious. I’ve been toying with it and discovering all kinds of amazing shortcuts I can’t wait to share with you guys.

Continue Reading: Streamlined Development with Gulp.js

Like what you read? Give Joshua Comeau a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.