Skip to main content

6 posts tagged with "jasmine"

View All Tags

Journalling the Migration of Jasmine Tests to TypeScript

I previously attempted to migrate my Jasmine tests from JavaScript to TypeScript. The last time I tried it didn't go so well and I bailed. Thank the Lord for source control. But feeling I shouldn't be deterred I decided to have another crack at it.

I did manage it this time... Sort of. Unfortunately there was a problem which I discovered right at the end. An issue with the TypeScript / Visual Studio tooling. So, just to be clear, this is not a blog post of "do this and it will work perfectly". On this occasion there will be some rough edges. This post exists, as much as anything else, as a record of the problems I experienced - I hope it will prove useful. Here we go:

What to Migrate?#

I'm going to use one of the test files in my my side project Proverb. It's the tests for an AngularJS controller called sageDetail - I've written about it before. Here it is in all it's JavaScript-y glory:

describe("Proverb.Web -> app-> controllers ->", function () {
beforeEach(function () {
module("app");
});
describe("sageDetail ->", function () {
var $rootScope,
getById_deferred, // deferred used for promises
$location, $routeParams_stub, common, datacontext, // controller dependencies
sageDetailController; // the controller
beforeEach(inject(function (_$controller_, _$rootScope_, _$q_, _$location_, _common_, _datacontext_) {
$rootScope = _$rootScope_;
$q = _$q_;
$location = _$location_;
common = _common_;
datacontext = _datacontext_;
$routeParams_stub = { id: "10" };
getById_deferred = $q.defer();
spyOn(datacontext.sage, "getById").and.returnValue(getById_deferred.promise);
spyOn(common, "activateController").and.callThrough();
spyOn(common.logger, "getLogFn").and.returnValue(jasmine.createSpy("log"));
spyOn($location, "path").and.returnValue(jasmine.createSpy("path"));
sageDetailController = _$controller_("sageDetail", {
$location: $location,
$routeParams: $routeParams_stub,
common: common,
datacontext: datacontext
});
}));
describe("on creation ->", function () {
it("controller should have a title of 'Sage Details'", function () {
expect(sageDetailController.title).toBe("Sage Details");
});
it("controller should have no sage", function () {
expect(sageDetailController.sage).toBeUndefined();
});
it("datacontext.sage.getById should be called", function () {
expect(datacontext.sage.getById).toHaveBeenCalledWith(10, true);
});
});
describe("activateController ->", function () {
var sage_stub;
beforeEach(function () {
sage_stub = { name: "John" };
});
it("should set sages to be the resolved promise values", function () {
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
expect(sageDetailController.sage).toBe(sage_stub);
});
it("should log 'Activated Sage Details View' and set title with name", function () {
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
expect(sageDetailController.log).toHaveBeenCalledWith("Activated Sage Details View");
expect(sageDetailController.title).toBe("Sage Details: " + sage_stub.name);
});
});
describe("gotoEdit ->", function () {
var sage_stub;
beforeEach(function () {
sage_stub = { id: 20 };
});
it("should set $location.path to edit URL", function () {
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
sageDetailController.gotoEdit();
expect($location.path).toHaveBeenCalledWith("/sages/edit/" + sage_stub.id);
});
});
});
});

Off we go#

Righteo. Let's flip the switch. sageDetail.js you shall go to the ball! One wave of my magic wand and sageDetail.js becomes sageDetail.ts... Alakazam!! Of course we've got to do the fiddling with the csproj file to include the dependent JavaScript files. (I'll be very pleased when ASP.Net vNext ships and I don't have to do this anymore....) So find this:

<TypeScriptCompile Include="app\sages\sageDetail.ts" />

And add this:

<Content Include="app\sages\sageDetail.js">
<DependentUpon>sageDetail.ts</DependentUpon>
</Content>
<Content Include="app\sages\sageDetail.js.map">
<DependentUpon>sageDetail.ts</DependentUpon>
</Content>

What next? I've a million red squigglies in my code. It's "could not find symbol" city. Why? Typings! We need typings! So let's begin - I'm needing the Jasmine typings for starters. So let's hit NuGet and it looks like we need this:

Install-Package jasmine.TypeScript.DefinitelyTypedThat did no good at all. Still red squigglies. I'm going to hazard a guess that this is something to do with the fact my JavaScript Unit Test project doesn't contain the various TypeScript artefacts that Visual Studio kindly puts into the web csproj for you. This is because I'm keeping my JavaScript tests in a separate project from the code being tested. Also, the Visual Studio TypeScript tooling seems to work on the assumption that TypeScript will only be used within a web project; not a test project. Well I won't let that hold me back... Time to port the TypeScript artefacts in the web csproj over by hand. I'll take this:

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props')" />

And I'll also take this

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<TypeScriptNoImplicitAny>True</TypeScriptNoImplicitAny>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets')" />

Bingo bango - a difference. I no longer have red squigglies under the Jasmine statements (describe, it etc). But alas, I do everywhere else. One in particular draws my eye...

Could not find symbol '$q'#

Once again TypeScript picks up the hidden bugs in my JavaScript:

$q = _$q_;

That's right it's an implicit global. Quickly fixed:

var $q = _$q_;

Typings? Where we're going, we need typings...#

We need more types. We're going to need the types created by our application; our controllers / services / directives etc. As well that we need the types used in the creation of the app. So the Angular typings etc. Since we're going to need to use reference statements to pull in the types created by our application I might as well use them to pull in the required definition files as well (eg angular.d.ts):

/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular-mocks.d.ts" />
/// <reference path="../../../proverb.web/app/sages/sagedetail.ts" />
/// <reference path="../../../proverb.web/app/common/common.ts" />
/// <reference path="../../../proverb.web/app/services/datacontext.ts" />
/// <reference path="../../../proverb.web/app/services/repository.sage.ts" />

Now we need to work our way through the "variable 'x' implicitly has an 'any' type" messages. One thing we need to do is to amend our original sageDetails.ts file so that the sageDetailRouteParams interface and SageDetail class are exported from the controllers module. We can't use the types otherwise. Now we can add typings to our file - once finished it looks like this:

/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular-mocks.d.ts" />
/// <reference path="../../../proverb.web/app/sages/sagedetail.ts" />
/// <reference path="../../../proverb.web/app/common/common.ts" />
/// <reference path="../../../proverb.web/app/services/datacontext.ts" />
/// <reference path="../../../proverb.web/app/services/repository.sage.ts" />
describe("Proverb.Web -> app-> controllers ->", function () {
beforeEach(function () {
module("app");
});
describe("sageDetail ->", function () {
var $rootScope: ng.IRootScopeService,
// deferred used for promises
getById_deferred: ng.IDeferred<sage>,
// controller dependencies
$location: ng.ILocationService,
$routeParams_stub: controllers.sageDetailRouteParams,
common: common,
datacontext: datacontext,
sageDetailController: controllers.SageDetail; // the controller
beforeEach(inject(function (
_$controller_: any,
_$rootScope_: ng.IRootScopeService,
_$q_: ng.IQService,
_$location_: ng.ILocationService,
_common_: common,
_datacontext_: datacontext) {
$rootScope = _$rootScope_;
var $q = _$q_;
$location = _$location_;
common = _common_;
datacontext = _datacontext_;
$routeParams_stub = { id: "10" };
getById_deferred = $q.defer();
spyOn(datacontext.sage, "getById").and.returnValue(getById_deferred.promise);
spyOn(common, "activateController").and.callThrough();
spyOn(common.logger, "getLogFn").and.returnValue(jasmine.createSpy("log"));
spyOn($location, "path").and.returnValue(jasmine.createSpy("path"));
sageDetailController = _$controller_("sageDetail", {
$location: $location,
$routeParams: $routeParams_stub,
common: common,
datacontext: datacontext
});
}));
describe("on creation ->", function () {
it("controller should have a title of 'Sage Details'", function () {
expect(sageDetailController.title).toBe("Sage Details");
});
it("controller should have no sage", function () {
expect(sageDetailController.sage).toBeUndefined();
});
it("datacontext.sage.getById should be called", function () {
expect(datacontext.sage.getById).toHaveBeenCalledWith(10, true);
});
});
describe("activateController ->", function () {
var sage_stub: sage;
beforeEach(function () {
sage_stub = { name: "John", id: 10, username: "John", email: "[email protected]", dateOfBirth: new Date() };
});
it("should set sages to be the resolved promise values", function () {
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
expect(sageDetailController.sage).toBe(sage_stub);
});
it("should log 'Activated Sage Details View' and set title with name", function () {
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
expect(sageDetailController.log).toHaveBeenCalledWith("Activated Sage Details View");
expect(sageDetailController.title).toBe("Sage Details: " + sage_stub.name);
});
});
describe("gotoEdit ->", function () {
var sage_stub: sage;
beforeEach(function () {
sage_stub = { name: "John", id: 20, username: "John", email: "[email protected]", dateOfBirth: new Date() };
});
it("should set $location.path to edit URL", function () {
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
sageDetailController.gotoEdit();
expect($location.path).toHaveBeenCalledWith("/sages/edit/" + sage_stub.id);
});
});
});
});

So That's All Good...#

Except it's not. When I run the tests using Chutzpah my sageDetail controller tests aren't found. My spider sense is tingling. This is something to do with the reference statements. They're throwing Chutzpah off. No bother, I can fix that with a quick tweak of the project file:

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<TypeScriptNoImplicitAny>True</TypeScriptNoImplicitAny>
<TypeScriptRemoveComments>True</TypeScriptRemoveComments>
</PropertyGroup>

The TypeScript compiler will now strip comments; which includes the reference statements. Now my tests are detected *and* they run. Yay!

Who Killed the TypeScript Language Service?#

Yup it's dead. Whilst the compilation itself has no issues, take a look at the errors being presented for just one of the files back in the original web project:

It looks like having one TypeScript project in a solution which uses reference comments somehow breaks the implicit referencing behaviour built into Visual Studio for other TypeScript projects in the solution. I can say this with some confidence as if I pull out the reference comments from the top of the test file that we've converted then it's business as usual - the TypeScript Language Service lives once more. I'm sure you can see the problem here though: the TypeScript test file doesn't compile. All rather unsatisfactory.

I suspect that if I added reference comments throughout the web project the TypeScript Language Service would be just fine. But I rather like the implicit referencing functionality so I'm not inclined to do that. After reaching something of a brick wall and thinking I had encountered a bug in the TypeScript Language service I raised an issue on GitHub.

Solutions....#

Thanks to the help of Mohamed Hegazy it emerged that the problem was down to missing reference comments in my sageDetail controller tests. One thing I had not considered was the 2 different ways each of my TypeScript projects were working:

  • Proverb.Web uses the Visual Studio implicit referencing functionality. This means that I do not need to use reference comments in the TypeScript files in Proverb.Web.
  • Proverb.Web.JavaScript does *not* uses the implicit referencing functionality. It needs reference comments to resolve references.

The important thing to take away from this (and the thing I had overlooked) was that Proverb.Web.JavaScript uses reference comments to pull in Proverb.Web TypeScript files. Those files have dependencies which are *not* stated using reference comments. So the compiler trips up when it tries to walk the dependency tree - there are no reference comments to be followed! So for example, common.ts has a dependency upon logger.ts. Fixing the TypeScript Language Service involves ensuring that the full dependency list is included in the sageDetail controller tests file, like so:

/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular-mocks.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/angularjs/angular-route.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/toastr/toastr.d.ts" />
/// <reference path="../../../proverb.web/scripts/typings/underscore/underscore.d.ts" />
/// <reference path="../../../proverb.web/app/sages/sagedetail.ts" />
/// <reference path="../../../proverb.web/app/common/logger.ts" />
/// <reference path="../../../proverb.web/app/common/common.ts" />
/// <reference path="../../../proverb.web/app/services/datacontext.ts" />
/// <reference path="../../../proverb.web/app/services/repositories.ts" />
/// <reference path="../../../proverb.web/app/services/repository.sage.ts" />
/// <reference path="../../../proverb.web/app/services/repository.saying.ts" />
/// <reference path="../../../proverb.web/app/app.ts" />
/// <reference path="../../../proverb.web/app/config.route.ts" />

With this in place you have a working solution, albeit one that is a little flaky. An alternative solution was suggested by Noel Abrahams which I quote here:

Why not do the following?

  • Compile Proverb.Web with --declarations and the option for combining output into a single file. This should create a Proverb.Web.d.ts in your output directory.
  • In Proverb.Web.Tests.JavaScript add a reference to this file.
  • Right-click Proverb.Web.Tests.JavaScript select "Build Dependencies" > "Project Dependencies" and add a reference to Proverb.Web.

I don't think directly referencing TypeScript source files is a good idea, because it causes the file to be rebuilt every time the dependant project is compiled.

Mohamed rather liked this solution. It looks like some more work is due to be done on the TypeScript tooling to make this less headache-y in future.

Unit Testing an Angular Controller with Jasmine

Anyone who reads my blog will know that I have been long in the habit of writing unit tests for my C# code. I'm cool like that. However, it took me a while to get up and running writing unit tests for my JavaScript code. I finally got there using a combination of Jasmine 2.0 and Chutzpah. (Jasmine being my test framework and Chutzpah being my test runner.)

I'm getting properly into the habit of testing my JavaScript. I won't pretend it's been particularly fun but I firmly believe it will end up being useful... That's what I tell myself during the long dark tea-times of the soul anyway.

I have a side project called Proverb. It doesn't do anything in particular - for the most part it's a simple application that displays the collected wise sayings of a team that I used to be part of. There's not much to it - a bit of CRUD, a dashboard. Not much more. Because of the project's simplicity it's ideal to use Proverb's underlying idea when trying out new technologies / frameworks. The best way to learn is to do. So if I want to learn "X", then building Proverb using "X" is a good way to go.

I digress already. I had a version of Proverb built using a combination of AngularJS and TypeScript. I had written the Angular side of Proverb without any tests. Now I was able to write JavaScript tests for my Angular code that's just what I set out to do. It should prove something of a of Code Kata too.

Whilst I'm at it I thought it might prove helpful if I wrote up how I approached writing unit tests for a single Angular controller. So here goes.

What I'm Testing#

I have an Angular controller called sagesDetail. It powers this screen:

sagesDetail is a very simple controller. It does these things:

  1. Load the "sage" (think of it as just a "user") and make it available on the controller so it can be bound to the view.
  2. Set the view title.
  3. Log view activation.
  4. Expose a gotoEdit method which, when called, redirects the user to the edit screen.

The controller is written in TypeScript and looks like this:

sagesDetail.ts#

module controllers {
"use strict";
var controllerId = "sageDetail";
interface sageDetailRouteParams extends ng.route.IRouteParamsService {
id: string;
}
class SageDetail {
log: loggerFunction;
sage: sage;
title: string;
static $inject = ["$location", "$routeParams", "common", "datacontext"];
constructor(
private $location: ng.ILocationService,
private $routeParams: sageDetailRouteParams,
private common: common,
private datacontext: datacontext
) {
this.sage = undefined;
this.title = "Sage Details";
this.log = common.logger.getLogFn(controllerId);
this.activate();
}
// Prototype methods
activate() {
var id = parseInt(this.$routeParams.id, 10);
var dataPromises: ng.IPromise<any>[] = [this.datacontext.sage.getById(id, true).then(data => this.sage = data)];
this.common.activateController(dataPromises, controllerId, this.title)
.then(() => {
this.log("Activated Sage Details View");
this.title = "Sage Details: " + this.sage.name;
});
}
gotoEdit() {
this.$location.path("/sages/edit/" + this.sage.id);
}
}
angular.module("app").controller(controllerId, SageDetail);
}

When compiled to JavaScript it looks like this:

sageDetail.js#

var controllers;
(function (controllers) {
"use strict";
var controllerId = "sageDetail";
var SageDetail = (function () {
function SageDetail($location, $routeParams, common, datacontext) {
this.$location = $location;
this.$routeParams = $routeParams;
this.common = common;
this.datacontext = datacontext;
this.sage = undefined;
this.title = "Sage Details";
this.log = common.logger.getLogFn(controllerId);
this.activate();
}
// Prototype methods
SageDetail.prototype.activate = function () {
var _this = this;
var id = parseInt(this.$routeParams.id, 10);
var dataPromises = [this.datacontext.sage.getById(id, true).then(function (data) {
return _this.sage = data;
})];
this.common.activateController(dataPromises, controllerId, this.title).then(function () {
_this.log("Activated Sage Details View");
_this.title = "Sage Details: " + _this.sage.name;
});
};
SageDetail.prototype.gotoEdit = function () {
this.$location.path("/sages/edit/" + this.sage.id);
};
SageDetail.$inject = ["$location", "$routeParams", "common", "datacontext"];
return SageDetail;
})();
angular.module("app").controller(controllerId, SageDetail);
})(controllers || (controllers = {}));
//# sourceMappingURL=sageDetail.js.map

Now for the Tests#

I haven't yet made the move of switching over my Jasmine tests from JavaScript to TypeScript. (It's on my list but there's only so many things you can do at once...) For that reason the tests you'll see here are straight JavaScript. Below you will see the tests for the sageDetail controller.

I have put very comments in the test code to make clear the intent to you, dear reader. Annotated the life out of them. Naturally I wouldn't expect a test to be so heavily annotated in a typical test suite - and you can be sure mine normally aren't!

Jasmine tests for sageDetail.js#

describe("Proverb.Web -> app-> controllers ->", function () {
// Before each test runs we're going to need ourselves an Angular App to test - go fetch!
beforeEach(function () {
module("app"); // module is an alias for <a href="https://docs.angularjs.org/api/ngMock/function/angular.mock.module">angular.mock.module</a>
});
// Tests for the sageDetail controller
describe("sageDetail ->", function () {
// Declare describe-scoped variables
var $rootScope,
getById_deferred, // deferred used for promises
$location, $routeParams_stub, common, datacontext, // controller dependencies
sageDetailController; // the controller
// Before each test runs set up the controller using inject - an alias for <a href="https://docs.angularjs.org/api/ngMock/function/angular.mock.inject">angular.mock.inject</a>
beforeEach(inject(function (_$controller_, _$rootScope_, _$q_, _$location_, _common_, _datacontext_) {
// Note how each parameter is prefixed and suffixed with "_" - this an Angular nicety
// which allows you to have variables in your tests with the original reference name.
// So here we assign the injected parameters to the describe-scoped variables:
$rootScope = _$rootScope_;
$q = _$q_;
$location = _$location_;
common = _common_;
datacontext = _datacontext_;
// Our controller has a dependency on an "id" property passed on the $routeParams
// We're going to stub this out with a JavaScript object literal
$routeParams_stub = { id: "10" };
// Our controller depends on a promise returned from this function: datacontext.sage.getById
// Well strictly speaking it also uses a promise for activateController but since the activateController
// promise just wraps the getById promise it will be resolved when the getById promise is.
// Here we create a deferred representing the getById promise which we can resolve as we need to
getById_deferred = $q.defer();
// set up a spy on datacontext.sage.getById and set it to return the promise of getById_deferred
// this allows us to #1 detect that getById has been called
// and #2 resolve / reject our promise as our test requires using getById_deferred
spyOn(datacontext.sage, "getById").and.returnValue(getById_deferred.promise);
// set up a spy on common.activateController and set it to call through
// this allows us to detect that activateController has been called whilst
// maintaining existing controller functionality
spyOn(common, "activateController").and.callThrough();
// set up spys on common.logger.getLogFn and $location.path so we can detect they have been called
spyOn(common.logger, "getLogFn").and.returnValue(jasmine.createSpy("log"));
spyOn($location, "path").and.returnValue(jasmine.createSpy("path"));
// create a sageDetail controller and inject the dependencies we have set up
sageDetailController = _$controller_("sageDetail", {
$location: $location,
$routeParams: $routeParams_stub,
common: common,
datacontext: datacontext
});
}));
// Tests for the controller state at the point of the sageDetail controller's creation
// ie before the getById / activateController promises have been resolved
// So this tests the constructor (function) and the activate function up to the point
// of the promise calls
describe("on creation ->", function () {
it("controller should have a title of 'Sage Details'", function () {
// tests this code has executed:
// this.title = "Sage Details";
expect(sageDetailController.title).toBe("Sage Details");
});
it("controller should have no sage", function () {
// tests this code has executed:
// this.sage = undefined;
expect(sageDetailController.sage).toBeUndefined();
});
it("datacontext.sage.getById should be called", function () {
// tests this code has executed:
// this.datacontext.sage.getById(id, true)
expect(datacontext.sage.getById).toHaveBeenCalledWith(10, true);
});
});
// Tests for the controller state at the point of the resolution of the getById promise
// ie after the getById / activateController promises have been resolved
// So this tests the constructor (function) and the activate function after the point
// of the promise calls
describe("activateController ->", function () {
var sage_stub;
beforeEach(function () {
// Create a sage stub which will be used when resolving the getById promise
sage_stub = { name: "John" };
});
it("should set sages to be the resolved promise values", function () {
// Resolve the getById promise with the sage stub
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
// tests this code has executed:
// this.sage = data
expect(sageDetailController.sage).toBe(sage_stub);
});
it("should log 'Activated Sage Details View' and set title with name", function () {
// Resolve the getById promise with the sage stub
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
// tests this code has executed:
// this.log("Activated Sage Details View");
// this.title = "Sage Details: " + this.sage.name;
expect(sageDetailController.log).toHaveBeenCalledWith("Activated Sage Details View");
expect(sageDetailController.title).toBe("Sage Details: " + sage_stub.name);
});
});
// Tests for the gotoEdit function on the controller
// Note that this will only be called *after* a controller has been created
// and it depends upon a sage having first been loaded
describe("gotoEdit ->", function () {
var sage_stub;
beforeEach(function () {
// Create a sage stub which will be used when resolving the getById promise
sage_stub = { id: 20 };
});
it("should set $location.path to edit URL", function () {
// Resolve the getById promise with the sage stub
getById_deferred.resolve(sage_stub);
$rootScope.$digest(); // So Angular processes the resolved promise
sageDetailController.gotoEdit();
// tests this code has executed:
// this.$location.path("/sages/edit/" + this.sage.id);
expect($location.path).toHaveBeenCalledWith("/sages/edit/" + sage_stub.id);
});
});
});
});

Running JavaScript Unit Tests in AppVeyor

With a little help from Chutzpah...#

AppVeyor (if you're not aware of it) is a Continuous Integration provider. If you like, it's plug-and-play CI for .NET developers. It's lovely. And what's more it's "free for open-source projects with public repositories hosted on GitHub and BitBucket". Boom! I recently hooked up 2 of my GitHub projects with AppVeyor. It took me all of... 10 minutes. If that? It really is *that* good.

But.... There had to be a "but" otherwise I wouldn't have been writing the post you're reading. For a little side project of mine called Proverb there were C# unit tests and there were JavaScript unit tests. And the JavaScript unit tests weren't being run... No fair!!!

Chutzpah is a JavaScript test runner which at this point runs QUnit, Jasmine and Mocha JavaScript tests. I use the Visual Studio extension to run Jasmine tests on my machine during development. I've also been able to use Chutzpah for CI purposes with Visual Studio Online / Team Foundation Server. So what say we try and do the triple and make it work with AppVeyor too?

NuGet me?#

In order that I could run Chutzpah I needed Chutzpah to be installed on the build machine. So I had 2 choices:

  1. Add Chutzpah direct to the repo
  2. Add the Chutzpah Nuget package to the solution

Unsurprisingly I chose #2 - much cleaner.

Now to use Chutzpah#

Time to dust down the PowerShell. I created myself a "before tests script" and added it to my build. It looked a little something like this:

# Locate Chutzpah
$ChutzpahDir = get-childitem chutzpah.console.exe -recurse | select-object -first 1 | select -expand Directory
# Run tests using Chutzpah and export results as JUnit format to chutzpah-results.xml
$ChutzpahCmd = "$($ChutzpahDir)\chutzpah.console.exe $($env:APPVEYOR_BUILD_FOLDER)\AngularTypeScript\Proverb.Web.Tests.JavaScript /junit .\chutzpah-results.xml"
Write-Host $ChutzpahCmd
Invoke-Expression $ChutzpahCmd
# Upload results to AppVeyor one by one
$testsuites = [xml](get-content .\chutzpah-results.xml)
$anyFailures = $FALSE
foreach ($testsuite in $testsuites.testsuites.testsuite) {
write-host " $($testsuite.name)"
foreach ($testcase in $testsuite.testcase){
$failed = $testcase.failure
$time = $testsuite.time
if ($testcase.time) { $time = $testcase.time }
if ($failed) {
write-host "Failed $($testcase.name) $($testcase.failure.message)"
Add-AppveyorTest $testcase.name -Outcome Failed -FileName $testsuite.name -ErrorMessage $testcase.failure.message -Duration $time
$anyFailures = $TRUE
}
else {
write-host "Passed $($testcase.name)"
Add-AppveyorTest $testcase.name -Outcome Passed -FileName $testsuite.name -Duration $time
}
}
}
if ($anyFailures -eq $TRUE){
write-host "Failing build as there are broken tests"
$host.SetShouldExit(1)
}

What this does is:

  1. Run Chutzpah from the installed NuGet package location, passing in the location of my Jasmine unit tests. In the case of my project there is a chutzpah.json file in the project which dictates how Chutzpah should run the tests. Also, the JUnit flag is also passed in order that Chutzpah creates a chutzpah-results.xml file of test results in the JUnit format.
  2. We iterate through test results and tell AppVeyor about the the test passes and failures using the Build Worker API.
  3. If there have been any failed tests then we fail the build. If you look here you can see a deliberately failed build which demo's that this works as it should.

That's a wrap - We now have CI which includes our JavaScript tests! That's right we get to see beautiful screens like these:

Thanks to...#

Thanks to Dan Jones, whose comments on this discussion provided a number of useful pointers which moved me in the right direction. And thanks to Feador Fitzner who has generously said AppVeyor will support JUnit in the future which may simplify use of Chutzpah with AppVeyor even further.

Migrating from AngularJS to AngularTS - a walkthrough

It started with nuns. Don't all good stories start that way? One of my (many) aunts is a Poor Clare nun. At some point in the distant past I was cajoled into putting together a simple website for her convent. This post is a walkthrough of how to migrate from AngularJS using JavaScript to AngularJS using TypeScript. It just so happens that the AngularJS app in question is the one that belongs to my mother's sister's convent.

TL;DR - grab what you need#

For reference the complete "before" and "after" projects can be found on GitHub here. This is available so people can see clearly what changes have been made in the migration.

The content of the site is available for reference only

. (Not that I can really imagine people creating their own "Poor Clares" site and hawking it to convents around the globe but I thought I'd make the point.) It looks like this:

Background#

I've been quietly maintaining this website / app for quite a while now. It's a very simple site; 95% of it is static content about the convent. The one piece of actual functionality is a page which allows the user of the website to send a prayer request to the nuns at the convent:

Behind the scenes this sends 2 emails:

  • The first back to the person who submitted the prayer request assuring them that they will be prayed for.
  • The second to the convent telling them the details of what the person would like prayer for.

Right now you are probably thinking this is an unusual post. Perhaps it is, but bear with me.

Over time the website has had many incarnations. It's been table-based layout, it's used Kendo UI, it's used Bootstrap. It's been static HTML, it's been ASP.Net WebForms, it's been ASP.Net MVC and it's currently built using AngularJS with MVC on the back-end to handle bundling / minification and dispatching of emails.

I decided to migrate this AngularJS app to use TypeScript. As I did that I thought I'd document the process for anyone else who might be considering doing something similar. As it happens this is a particularly good candidate for migration as there's a full unit test suite for the app (written with Jasmine). Once I've finished the migration these unit tests should pass, just as they do currently.

You are probably thinking to yourself "but TypeScript is just about adding compile-time annotations right? How could the unit tests not pass after migration?" Fair point, well made. Well that is generally true but I have something slightly different planned when we get to the controllers - you'll see what I mean...

It's also a good candidate for documenting a walkthrough as it's a particularly small and simple Angular app. It consists of just 3 controllers, 2 services and 1 app.

Before I kick off I thought I'd list a couple of guidelines / caveats on this post:

  • I don't intend to say much about the architecture of this application - I want to focus on the migration from JavaScript to TypeScript.
  • The choices that I make for the migration path do not necessarily reflect the "one true way". Rather, they are pragmatic choices that I am making - there may be alternatives approaches here and there that could be used instead.
  • I love Visual Studio - it's my IDE of choice and the one I am using as I perform the migration. Some of the points that I will make are Visual Studio specific - I will try and highlight that when appropriate.

Typings#

The first thing we're going to need to get going are the Angular typing files which can be found on Definitely Typed here. Since these typings are made available over NuGet I'm going to pull them in with a wave of my magic Install-Package angularjs.TypeScript.DefinitelyTyped.

As well as pulling in the typing files Visual Studio 2013 has also made some tweaks to my PoorClaresAngular.csproj file which it tells me about:

And these are the TypeScript specific additions that Visual Studio has made to PoorClaresAngular.csproj:

<Import
Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props"
Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props')" />
<TypeScriptToolsVersion>1.0</TypeScriptToolsVersion>
<Import
Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets"
Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets')" />

I'm going to add one extra of my own:

<TypeScriptNoImplicitAny>True</TypeScriptNoImplicitAny>

This prevents you having variables of type any in your TypeScript codebase without you implicitly specifying the type. You can live without this but I've found it's useful to catch where you're missing out on the benefit of static typing. Further to that, this option can be particularly useful when performing a migration. It will become obvious why this is the case as we go on.

I decline the kind opportunity to further search NuGet as I'm already on my way typing-wise. So let's review what has happened. Below you can see the typing files that have been pulled in and that the project and packages files were amended:

Changing JS files to TS files#

This really should be as simple as changing all the JavaScript files underneath the js directory to have the suffix ts. So going from this:

To this:

And if you're not using Visual Studio it is. But if you are using Visual Studio there's a certain amount of fiddling required to include the generated .js and .js.map files associated with each .ts file. The easiest (hah!) thing to do is to crack open the project and wherever you find a &lt;TypeScriptCompile Include="js\somePath.ts" /&gt; to add in 2 Content statements, one for each generated file which states the dependency on the TypeScript file. For example:

<TypeScriptCompile Include="js\services\siteSectionService.ts" />
<Content Include="js\services\siteSectionService.js">
<DependentUpon>siteSectionService.ts</DependentUpon>
</Content>
<Content Include="js\services\siteSectionService.js.map">
<DependentUpon>siteSectionService.ts</DependentUpon>
</Content>

It's a bit of a pain to have to do this at the moment. Hopefully the Visual Studio tooling will catch up so this sort of tweaking becomes unnecessary.

Recap#

So, where are we? Well, we've got our project ready for TypeScript, we've pulled in the Angular typings from Definitely Typed and we've turned all our JavaScript files in the js directory into TypeScript files.

Now we can actually start working through our TypeScript files and ensuring we're all typed correctly. Please note that because I'm working in Visual Studio I get the benefit of implicit referencing; I don't have to explicitly state the typing files each TypeScript file relies on at the head of the file (eg /// &lt;reference path="angularjs/angular.d.ts" /&gt;). If you aren't working in Visual Studio then you'd need to add these yourself.

TypeScriptify app.ts#

Opening up app.ts we're presented with a few red squigglies:

These red squigglies are the direct result of my earlier opting in to NoImplicitAny. So in my view it's already paid for itself as it's telling me where I could start using typings. So to get things working nicely I'll give $routeProvider the type of ng.route.IRouteProvider and I'll explicitly specify the type of any for the 2 params parameters:

// ...
function ($routeProvider: ng.route.IRouteProvider) {
function getTheConventTemplateUrl(params: any) {
var view = params.view || "home";
return "partials/theConvent/" + view + ".html";
}
function getMainTemplateUrl(params: any) {
var view = params.view || "home";
return "partials/main/" + view + ".html";
}
// ...
}
// ...

TypeScriptify siteSectionService.ts#

Opening up siteSectionService.ts we're only presented with a single squiggly, and for the same reason as last time:

This error is easily remedied by giving path the type of string.

What's more interesting / challenging is thinking about how we want to enforce the definition of siteSectionService. Remember, this is a service and as such it will be re-used elsewhere in the application (in both navController and mainController). What we need is an interface that describes what our (revealing module pattern) service exposes:

"use strict";
interface ISiteSectionService {
getSiteSection: () => string;
determineSiteSection: (path: string) => void;
}
angular.module("poorClaresApp.services").factory(
"siteSectionService",
[ // No dependencies at present
function (): ISiteSectionService {
var siteSection = "home";
function getSiteSection() {
return siteSection;
}
function determineSiteSection(path: string) {
var newSiteSection = "home";
if (path.indexOf("/theConvent/") !== -1) {
newSiteSection = "theConvent";
}
else if (path !== "/") {
newSiteSection = "main";
}
siteSection = newSiteSection;
}
return {
getSiteSection: getSiteSection,
determineSiteSection: determineSiteSection
};
}]);

As you can see the ISiteSectionService interface is marked as the return type of the function. This ensures that what we return from the function satisfies that definition. Also, it allows us to re-use that interface elsewhere (as we will do later).

TypeScriptify prayerRequestService.ts#

Opening up prayerRequestService.ts we're again in NoImplicitAny country:

This is fixed up by defining $http as ng.IHttpService and email and prayFor as string.

As with siteSectionService we need to create an interface to define what prayerRequestService exposes. This leaves us with this:

"use strict";
interface IPrayerRequestService {
sendPrayerRequest: (email: string, prayFor: string) => ng.IPromise<{
success: boolean;
text: string;
}>;
}
angular.module("poorClaresApp.services").factory(
"prayerRequestService",
["$http",
function ($http: ng.IHttpService): IPrayerRequestService {
var url = "/PrayerRequest";
function sendPrayerRequest(email: string, prayFor: string) {
var params = { email: email, prayFor: prayFor };
return $http.post(url, params)
.then(function (response) {
return {
success: response.data.success,
text: response.data.text
};
});
}
return {
sendPrayerRequest: sendPrayerRequest
};
}]);

TypeScriptify prayerRequestController.ts#

Opening up prayerRequestController.ts leads me to the conclusion that I have no interesting way left of telling you that we once more need to supply types for our parameters. Let's take it as read that the same will happen on all remaining files as well eh? Hopefully by now it's fairly clear that this option is useful, even if only for a migration. I say this because using it forces you to think about what typings should be applied to your code:

We'll define $scope as ng.IScope, prayerRequestService as IPrayerRequestService (which we created just now) and prayerRequest as { email: string; prayFor: string }. Which leaves me with this:

"use strict";
angular.module("poorClaresApp.controllers").controller(
"PrayerRequestController",
["$scope", "prayerRequestService",
function ($scope: ng.IScope, prayerRequestService: IPrayerRequestService) {
var vm = this;
vm.send = function (prayerRequest: { email: string; prayFor: string }) {
vm.message = {
success: true,
text: "Sending..."
};
prayerRequestService.sendPrayerRequest(prayerRequest.email, prayerRequest.prayFor)
.then(function (response) {
vm.message = {
success: response.success,
text: response.text
};
})
.then(null, function (error) { // IE 8 friendly alias for catch
vm.message = {
success: false,
text: "Sorry your email was not sent"
};
});
}
}]);

I could move on but let's go for bonus points (and now you'll see why the unit test suite is so handy). To quote the Angular documentation:

In Angular, a Controller is a JavaScript constructor function that is used to augment the Angular Scope.

So let's see if we can swap over our vanilla contructor function for a TypeScript class. This will (in my view) better express the intention of the code. To do this I am essentially following the example laid down by my Definitely Typed colleague Basarat. I highly recommend his screencast on the topic. Also kudos to Andrew Davey whose post on the topic also fed into this.

"use strict";
module poorClaresApp.controllers {
class PrayerRequestController {
static $inject = ["$scope", "prayerRequestService"];
constructor(
private $scope: ng.IScope,
private prayerRequestService: IPrayerRequestService) {
}
message: { success: boolean; text: string };
send(prayerRequest: { email: string; prayFor: string }) {
this.message = {
success: true,
text: "Sending..."
};
this.prayerRequestService.sendPrayerRequest(prayerRequest.email, prayerRequest.prayFor)
.then((response) => {
this.message = {
success: response.success,
text: response.text
};
})
.then(null, (error) => { // IE 8 friendly alias for catch
this.message = {
success: false,
text: "Sorry your email was not sent"
};
});
}
}
angular.module("poorClaresApp.controllers")
.controller("PrayerRequestController", PrayerRequestController);
}

My only reservation with this approach is that we have to declare the TypeScript class outside the angular.module... statement. To avoid cluttering up global scope I've placed our class in a module called poorClaresApp.controllers which maps nicely to our Angular module name. It would be nice if I could place the class definition in an IIFE to completely keep this completely isolated but TypeScript doesn't allow for that syntax (for reasons I'm unclear about - the output would be legal JavaScript).

For a small class this seems to add a little noise but as classes grow in complexity I think this approach will quickly start to pay dividends. There are a few things worth noting about the above approach:

  • The required injectable parameters have moved into the class definition in the form of the static $inject statement. I personally like that this no longer sits outside the code it relates to.
  • Because we're using TypeScript arrow functions (which preserve the outer "this" context) we are now free to dispose of the var vm = this; mechanism we're were previously using for the same purpose. Much more intuitive code to my mind.
  • We are not actually using $scope at all in this controller - maybe it should be removed entirely in the long run.

TypeScriptify navController.ts#

navController can be simply converted like so:

"use strict";
interface INavControllerScope extends ng.IScope {
isCollapsed: boolean;
siteSection: string;
}
angular.module("poorClaresApp.controllers").controller(
"NavController",
["$scope", "siteSectionService",
function ($scope: INavControllerScope, siteSectionService: ISiteSectionService) {
$scope.isCollapsed = true;
$scope.siteSection = siteSectionService.getSiteSection();
$scope.$watch(siteSectionService.getSiteSection, function (newValue, oldValue) {
$scope.siteSection = newValue;
});
}]);

I'd draw your attention to the creation of a the INavControllerScope interface that extends the default Angular $scope of ng.IScope with 2 extra properties.

Let's also switch this over to the class based approach (there is less of a reason to on this occasion just looking at the size of the codebase but I'm all about consistency of approach):

"use strict";
module poorClaresApp.controllers {
interface INavControllerScope extends ng.IScope {
isCollapsed: boolean;
siteSection: string;
}
class NavController {
static $inject = ["$scope", "siteSectionService"];
constructor(
private $scope: INavControllerScope,
private siteSectionService: ISiteSectionService) {
$scope.isCollapsed = true;
$scope.siteSection = siteSectionService.getSiteSection();
$scope.$watch(siteSectionService.getSiteSection, function (newValue, oldValue) {
$scope.siteSection = newValue;
});
}
}
angular.module("poorClaresApp.controllers").controller("NavController", NavController);
}

TypeScriptify mainController.ts#

Finally, mainController can be converted as follows:

"use strict";
angular.module("poorClaresApp.controllers").controller(
"MainController",
["$location", "siteSectionService",
function ($location: ng.ILocationService, siteSectionService: ISiteSectionService) {
siteSectionService.determineSiteSection($location.path());
}]);

Again it's just a case of assigning the undeclared types. For completeness lets also switch this over to the class based approach:

"use strict";
module poorClaresApp.controllers {
class MainController {
static $inject = ["$location", "siteSectionService"];
constructor(
private $location: ng.ILocationService,
private siteSectionService: ISiteSectionService) {
siteSectionService.determineSiteSection($location.path());
}
}
angular.module("poorClaresApp.controllers").controller("MainController", MainController);
}

Did it work? Drum roll...#

In unit tests we trust. Let's run them...

Success! I hope you found this useful.

Team Foundation Server, Continuous Integration and separate projects for JavaScript unit tests

Do you like to separate out your unit tests from the project you are testing? I imagine so. My own practice when creating a new project in Visual Studio is to create a separate unit test project alongside whose responsibility is to house unit tests for that new project.

When I check in code for that project I expect the continuous integration build to kick off and, as part of that, the unit tests to be run. When it comes to running .NET tests then Team Foundation Server (and it's cloud counterpart Visual Studio Online) has your back. When it comes to running JavaScript tests then... not so much.

This post will set out:

  1. How to get JavaScript tests to run on TFS / VSO in a continuous integration scenario.
  2. How to achieve this *without* having to include your tests as part of web project.

To do this I will lean heavily (that's fancy language for "rip off entirely") on an excellent blog post by Mathew Aniyan which covers point #1. My contribution is point #2.

Points #1 and #2 in short order#

First of all, install Chutzpah on TFS / VSO. You can do this by following Steps 1 - 6 from Mathew Aniyan's post. Instead of following steps 7 and 8 create a new unit test project in your solution.

Edit 29/05/2014: Matthew Manela (creator of Chutzpah) has confirmed that this is the correct approach - thanks chap!

@johnny_reilly Nope that is pretty much what you need to do.

鈥 Matthew Manela (@mmanela) May 15, 2014

To our unit test project add your JavaScript unit tests. These should be marked in Visual Studio with a Build Action of "Content" and a Copy to Output Directory of "Do not copy". You should be able to run these tests locally using the Visual Studio Chutzpah extension - or indeed in some other JavaScript test runner. Then, and this is the important part, edit the csproj file of your unit test project and add this Import Project statement:

<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />

Ordering is important in this case. It matters that this new statement sits after the other Import Project statements. So you should end up with a csproj file that looks in part like this: (comments added by me for clarity)

<!-- Pre-existing Import Project statements start -->
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- Pre-existing Import Project statements end -->
<!-- New addition start -->
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<!-- New addition end -->

Check in your amended csproj and the unit tests to TFS / VSO. You should see the JavaScript unit tests being run as part of the build.

The Surprisingly Happy Tale of Visual Studio Online, Continous Integration and Chutzpah

Going off piste#

The post that follows is a slightly rambly affair which is pretty much my journal of the first steps of getting up and running with JavaScript unit testing. I will not claim that much of this blog is down to me. In fact in large part is me working my way through Mathew Aniyan's excellent blog post on integrating Chutzpah with TFS. But a few deviations from this post have made me think it worth keeping hold of this record for my benefit (if no-one else's).

That's the disclaimers out of the way now...

...Try, try, try again...#

Getting started with JavaScript unit testing has not been the breeze I鈥檇 expected. Frankly I鈥檝e found the docs out there not particularly helpful. But if at first you don't succeed then try, try, try again.

So after a number of failed attempts I鈥檓 going to give it another go. Rushaine McBean says Jasmine is easiest so I'm going to follow her lead and give it a go.

Let鈥檚 new up a new (empty) ASP.NET project. Yes, I know ASP.NET has nothing to do with JavaScript unit testing but my end goal is to be able to run JS unit tests in Visual Studio and as part of Continuous Integration. Further to that, I'm anticipating a future where I have a solution that contains JavaScript unit tests and C# unit tests as well. It is indeed a bold and visionary Brave New World. We'll see how far we get.

First up, download Jasmine from GitHub - I'll use v2.0. Unzip it and fire up SpecRunner.html and whaddya know... It works!

As well it might. I鈥檇 be worried if it didn鈥檛. So I鈥檒l move the contents of the release package into my empty project. Now let鈥檚 see if we can get those tests running inside Visual Studio. I鈥檇 heard of Chutzpah which describes itself thusly:

鈥淐hutzpah is an open source JavaScript test runner which enables you to run unit tests using QUnit, Jasmine, Mocha, CoffeeScript and TypeScript.鈥

What I鈥檓 after is the Chutzpah test adapter for Visual Studio 2012/2013 which can be found here. I download the VSIX and install. Pretty painless. Once I restart Visual Studio I can see my unit tests in the test explorer. Nice! Run them and...

All fail. This makes me sad. All the errors say 鈥淐an鈥檛 find variable: Player in file鈥. Hmmm. Why? Dammit I鈥檓 actually going to have to read the documentation... It turns out the issue can be happily resolved by adding these 3 references to the top of PlayerSpec.js:

/// <reference path="../src/Player.js" />
/// <reference path="../src/Song.js" />
/// <reference path="SpecHelper.js" />

Now the tests pass:

The question is: can we get this working with Visual Studio Online?

Fortunately another has gone before me. Mathew Aniyan has written a superb blog post called "Javascript Unit Tests on Team Foundation Service with Chutzpah". Using this post as a guide (it was written 18 months ago which is frankly aeons in the world of the web) I'm hoping that I'll be able to, without too many tweaks, get Javascript unit tests running on Team Foundation Service / Visual Studio Online ( / insert this weeks rebranding here).

First of all in Visual Studio Online I鈥檒l create a new project called "GettingStartedWithJavaScriptUnitTesting" (using all the default options). Apparently 鈥淵our project is created and your team is going to absolutely love this.鈥 Hmmmm... I think I鈥檒l be judge of that.

Let's navigate to the project. I'll fire up Visual Studio by clicking on the 鈥淥pen in Visual Studio鈥 link. Once fired up and all the workspace mapping is sorted I鈥檒l move my project into the GettingStartedWithJavaScriptUnitTesting folder that now exists on my machine and add this to source control.

Back to Mathew's blog. It suggests renaming Chutzpah.VS2012.vsix to Chutzpah.VS2012.zip and checking certain files into TFS. I think Chutzpah has changed a certain amount since this was written. To be on the safe side I鈥檒l create a new folder in the root of my project called Chutzpah.VS2012 and put the contents of Chutzpah.VS2012.zip in there and add it to TFS (being careful to ensure that no dll鈥檚 are excluded).

Then I'll follow steps 3 and 4 from the blog post:

*3. In Visual Studio, Open Team Explorer & connect to Team Foundation Service. Bring up the Manage Build Controllers dialog. [Build 鈥> Manage Build Controllers] Select Hosted Build Controller Click on Properties button to bring up the Build Controller Properties dialog.

4. Change Version Control Path to custom Assemblies to refer to the folder where you checked in the binaries in step 2.

In step 5 the blog tells me to edit my build definition. Well I don鈥檛 have one in this new project so let鈥檚 click on 鈥淣ew Build Definition鈥, create one and then follow step 5:

*5. In Team Explorer, go to the Builds section and Edit your Build Definition which will run the javascript tests. Click on the Process tab Select the row named Automated Tests. Click on 鈥 button next to the value.

Rather than following step 6 (which essentially nukes the running of any .NET tests you might have) I chose to add another row by clicking "Add". In the dialog presented I changed the Test assembly specification to **\*.js and checked the "Fail build on test failure" checkbox.

Step 7 says:

*7. Create your Web application in Visual Studio and add your Qunit or Jasmine unit tests to them. Make sure that the js files (that contain the tests) are getting copied to the build output directory.

The picture below step 7 suggests that you should be setting your test / spec files to have a Copy to Output Directory setting of Copy always. This did not work for me!!! Instead, setting a Build Action of Content and a Copy to Output Directory setting of Do not copy did work.

Finally I checked everything into source control and queued a build. I honestly did not expect this to work. It couldn鈥檛 be this easy could it? And...

Wow! It did! Here鈥檚 me cynically expecting some kind of 鈥減ermission denied鈥 error and it actually works! Brilliant! Look up in the cloud it says the same thing!

Fantastic!

I realise that I haven鈥檛 yet written a single JavaScript unit test of my own and so I鈥檝e still a way to go. What I have done is quietened those voices in my head that said 鈥渢here鈥檚 not too much point having a unit test suite that isn鈥檛 plugged into continuous integration鈥. Although it's not documented here I'm happy to be able to report that I have been able to follow the self-same instructions for Team Foundation Service / Visual Studio Online and get CI working with TFS 2012 on our build server as well.

Having got up and running off the back of other peoples hard work I best try and write some of my own tests now....