TypeScript: Organizing your code with AMD modules and require.js

TypeScript has two methods of generating JavaScript output files: CommonJS, and AMD.  CommonJS is the default, and amd modules can be generated by adding the –module AMD option to the compiler flags.

In this post, I’ll show how to create and use AMD modules, as well as configuring require.js by using require.config.

Update

This article has been updated to use TypeScript 0.9.  You can download / browse the source at github/blorkfish/typescript-amd-require-0.9

The older 0.8.1 source for this solution can be found here.

The older 0.8.0 source for this solution can be found here

Mastering TypeScript Book : available April 2015

Over the past couple of months, I have been working very closely with the publishing team at PAKT Publishing on a new book called “Mastering TypeScript”.  It is scheduled for publication in April 2015.  You can read all about it here:https://www.packtpub.com/web-development/mastering-typescript.

B03967_MockupCover_Normal

 

Creating a default project using CommonJS

Let’s start with a standard new TypeScript project – which by default creates an app.ts file, and a default.htm – and  add the following:

  • \app directory (for application files)
  • \app\classes (for our AMD classes)
  • \lib directory (for external libraries)
  • \modules (for our module definitions)
  • \app\AppMain.ts  ( note that you should remove any code that the compiler generates in this file)
  • \app\AppConfig.ts ( remove any code )
  • \app\classes\Greeter.ts ( remove any code )
  • download require.js and include it in the \lib directory. ( require.js can be found here release 2.1.8 )
  • download require.d.ts from DefinitelyTyped, and save it in the modules directory.

require_amd_1

\modules\Require.d.ts

As at the time of update, this is at version 2.1.1.

\app\classes\Greeter.ts as an AMD module

Cut the code defining the Greeter class from \app.ts into the \app\classes\Greeter.ts file:

Effectively, we are now starting to organise our project, with one .ts file for each class.

app\classes\Greeter.ts:
class Greeter {
    element: HTMLElement;
    span: HTMLElement;
    timerToken: number;

    constructor (element: HTMLElement) { 
        this.element = element;
        this.element.innerText += "The time is: ";
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.span.innerText = new Date().toUTCString();
    }

    start() {
        this.timerToken = setInterval(() => this.span.innerText = new Date().toUTCString(), 500);
    }

    stop() {
        clearTimeout(this.timerToken);
    }

}

Compiling the project now should show the error: Could not find symbo ‘Greeter’.

Let’s fix this first by using a CommonJS reference – add a reference path to app.ts:

app.ts
/// <reference path="app/classes/Greeter.ts" />

window.onload = () => {
    var el = document.getElementById('content');
    var greeter = new Greeter(el);
    greeter.start();
};

The project should now compile.

If you run the project now, (using Internet Explorer), the Greeter.js file will be unreferenced:

0x800a1391 – JavaScript runtime error: ‘Greeter’ is undefined

The simple solution is to include this new Greeter.js file in default.htm:

default.htm
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script type="text/javascript" src="app/classes/Greeter.js"></script>
    <script src="app.js"></script>
</head>

Running the project now will succeed:

typescript_amd_1

Converting Greeter.ts to an AMD module

TypeScript 0.9 and upwards will default to compile all source files as AMD compliant.  This is slightly different to 0.8 versions, where by default projects were compiled to commonJS.  For reference purposes, the following section shows how to use AMD in 0.8 versions.  If using TypeScript 0.9, please continue to the next section, Export Greeter.

Specifying AMD compilation for 0.8 and 0.8.1 versions of TypeScript:

To compile project files to AMD modules, unload your project file, edit it, and add the –module AMD option to the command line options:

0.8.1

:  Here is the 0.8.1 version of the project file:

Note that you will need to remove the –sourcemap option for Debug configuration, as sourcemap and AMD do not work well together.

  <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <!--remove the --sourcemap option below-->
    <TypeScriptSourceMap></TypeScriptSourceMap>
  </PropertyGroup>
  <Target Name="BeforeBuild">
    <Message Text="Compiling TypeScript files" />
    <Message Text="Executing tsc$(TypeScriptSourceMap) @(TypeScriptCompile ->'&quot;%(fullpath)&quot;', ' ')" />
    <Exec Command="tsc$(TypeScriptSourceMap) --module AMD @(TypeScriptCompile ->'&quot;%(fullpath)&quot;', ' ')" />
  </Target>

Older version 0.8.0 compiler version:

  <Target Name="BeforeBuild">
    <Exec Command="&quot;$(PROGRAMFILES)\Microsoft SDKs\TypeScript.8.0.0\tsc&quot; --module AMD @(TypeScriptCompile ->'&quot;%(fullpath)&quot;', ' ')" />
  </Target>

Export Greeter

Before we change app\classes\Greeter.ts to an AMD module, have a look at the generated javascript source :

var Greeter = (function () {
    function Greeter(element) {
        this.element = element;
        this.element.innerHTML += "The time is: ";
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.span.innerText = new Date().toUTCString();
    }
    Greeter.prototype.start = function () {
        var _this = this;
        this.timerToken = setInterval(function () {
            return _this.span.innerHTML = new Date().toUTCString();
        }, 500);
    };

    Greeter.prototype.stop = function () {
        clearTimeout(this.timerToken);
    };
    return Greeter;
})();
//@ sourceMappingURL=Greeter.js.map

Now modify the Greeter class definition, and add the export keyword:

app/classes/Greeter.ts
export class Greeter {

AMD compliant javascript source.

After compiling, Note the changes to the javascript source – the entire code block has been wrapped in a define( […] ) block, and there is an extra exports.Greeter = Greeter; line at the bottom of the file:

define(["require", "exports"], function(require, exports) {
    var Greeter = (function () {
        function Greeter(element) {
            this.element = element;
            this.element.innerHTML += "The time is: ";
            this.span = document.createElement('span');
            this.element.appendChild(this.span);
            this.span.innerText = new Date().toUTCString();
        }
        Greeter.prototype.start = function () {
            var _this = this;
            this.timerToken = setInterval(function () {
                return _this.span.innerHTML = new Date().toUTCString();
            }, 500);
        };

        Greeter.prototype.stop = function () {
            clearTimeout(this.timerToken);
        };
        return Greeter;
    })();
    exports.Greeter = Greeter;
});
//@ sourceMappingURL=Greeter.js.map

Compiling at this stage will generate errors : Could not find symbol ‘Greeter’.

We now need to modify the app.ts file to import the the module.  Remove the ///reference path line, and add an import statement as below:

Now use the name of the import ( gt ) to reference gt.Greeter :

app.ts
import gt = module("app/classes/Greeter");

window.onload = () => {
    var el = document.getElementById('content');
    var greeter = new gt.Greeter(el);
    greeter.start();
};

Running the app at this stage now will produce the following error:

Unhandled exception at line 1, column 1 in http://localhost:8524/app.js

0x800a1391 – JavaScript runtime error: ‘define’ is undefined

This error is because define is part of the require.js library, as seen at the end of the require.d.ts file :

// Ambient declarations for 'require' and 'define'
declare var require: Require;
declare var requirejs: Require;
declare var req: Require;
declare var define: RequireDefine;

Configuring require.js

In order to use AMD modules, we need to tell our page to include require.js.  Looking at the require.js documentation, the way to do this is to include the following in your html page

default.htm :

<script data-main="app/AppConfig" type="text/javascript" src="lib/require.js"></script>

Note that the require.js syntax is to use the data-main property to specify a JavaScript file to load as the initial starting point for the application – in this case : app/AppConfig.js.

Remove the reference to Greeter.js, and app.js, so that your default.htm file looks like this:

<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script data-main="app/AppConfig" type="text/javascript" src="lib/require.js"></script>
</head>
<body>
    <h1>TypeScript HTML App</h1>

    <div id="content"></div>
</body>
</html>

AppConfig.ts

Create an app/AppConfig.ts TypeScript file, as follows:

app/AppConfig.ts
/// <reference path="../modules/require.d.ts" />

import gt = module("classes/Greeter");

require([], () => {
    // code from window.onload
    var el = document.getElementById('content');
    var greeter = new gt.Greeter(el);
    greeter.start();
});

Note that we have moved the application startup code (the window.onload function) into the body of the require function.

The app should now run using AMD loading.

Adding further modules

Should your application require further AMD modules, simply include them in the /lib directory, and specify them in the first array  as follows:

app/AppConfig.ts
require(['../lib/jquery-1.7.2','../lib/underscore', '../lib/backbone', '../lib/console'], () => {
    // code from window.onload
    var el = document.getElementById('content');
    var greeter = new gt.Greeter(el);
    greeter.start();
});

Note that the paths for require are relevant to the location of the AppConfig.ts file (which is in the app directory).

Using require.config

require.js has a number of configuration options that make it so powerful.  Among these is the ability to define dependencies between modules.

Unfortunately, including a require.config in our app/AppConfig.ts file as shown below will result in a run-time error:

0x800a01b6 – JavaScript runtime error: Object doesn’t support property or method ‘config’

app/AppConfig.ts
/// <reference path="../modules/require.d.ts" />

// the config below will cause a run-time error
require.config({
    baseUrl: '../'
});

import gt = module("classes/Greeter");

require(['../lib/jquery-1.7.2','../lib/underscore', '../lib/backbone', '../lib/console'], () => {
    // code from window.onload
    var el = document.getElementById('content');
    var greeter = new gt.Greeter(el);
    greeter.start();
});

This run-time error is caused because the TypeScript compiler ( with –module AMD ) compile option wraps the entire file in a require statement.  Have a look at the generated code:

app/AppConfig.js (generated)
define(["require", "exports", "classes/Greeter"], function (require, exports, __gt__) {
    // this require.config below should NOT be inside the define function
    require.config({
        baseUrl: '../'
    });
    var gt = __gt__;

    require([
        '../lib/jquery-1.7.2', 
        '../lib/underscore', 
        '../lib/backbone', 
        '../lib/console'
    ], function () {
        var el = document.getElementById('content');
        var greeter = new gt.Greeter(el);
        greeter.start();
    });
})

Using require.config with AMD modules solution:

The solution here is to separate our require config file from our application main file, and remove any import module statements from the configuration file.  Remember how the generated javascript changed when we added the import statement to app/classes/Greeter.ts ? So make sure that the file with require.config does not have any import statements :

AppMain.ts

Create an AppMain.ts file within the app folder as follows:

app/AppMain.ts
import gt = module("classes/Greeter");

export class AppMain {
    public run() {
        var el = document.getElementById('content');
        var greeter = new gt.Greeter(el);
        greeter.start();

    }
}

Modify AppConfig.ts to use named require parameters

app/AppConfig.ts
/// <reference path="../modules/require.d.ts" />
/// <reference path="AppMain.ts" />
require.config({
    //baseUrl: '../' // commented for now
});

require(['AppMain', 
    '../lib/jquery-1.7.2','../lib/underscore', '../lib/backbone', '../lib/console' ], 
    (main) => {
    // code from window.onload
    var appMain = new main.AppMain();
    appMain.run();
});

Note that we have specified to require.js that it must load a file named ‘AppMain’ ( our AppMain.ts compiled file), and that when the require function runs, AppMain’s classes will be referenced by the named parameter main. If we were to name all of the parameters in the require array, our code would look something like this:

app/AppConfig.ts
require(['AppMain', 
    '../lib/jquery-1.7.2','../lib/underscore', '../lib/backbone', '../lib/console' ], 
    (main, $, _, console) => {
    var appMain = new main.AppMain();
    appMain.run();
});

Using named require parameters and shims

Fortunately, require.js allows us to configure named parameters via the paths config variable, and then define a shim property for each named path, that includes the export symbol, and any dependencies, as follows:

app/AppConfig.ts
require.config({
    baseUrl: '../',
    paths: {
        'jquery': 'lib/jquery-1.7.2',
        'underscore': 'lib/underscore',
        'backbone': 'lib/backbone',
        'console': 'lib/console'
    }, 
    shim: {
        jquery: {
            exports: '$'
        },
        underscore: {
            exports: '_'
        },
        backbone: {
            deps: ["underscore", "jquery"],
            exports: "Backbone"
        },
        console: {
            exports: "console"
        }
    }
});

require([
     'jquery'
    , 'underscore'
    , 'backbone'
    , 'console'
    ], 
    ($, _, Backbone, console) => {
    $(() => {

        // code goes here

    });
});

Require.config tips

Note that for jquery plugins – all jquery extension methods must export to $ as well – this is accomplished by specifying $ as the export for plugins, and jquery as a dependency.  Note too that in the require function definition, TypeScript will not allow multiple parameters with the same name, so to use jquery.flip.js for example, use the following require.config:

require.config({
    paths: {
        'jquery': 'lib/jquery-1.7.2',
        'jqueryflip': 'lib/jquery-flip'
    },
    shim: {
        jquery: {
            exports: '$'
        },
        jqueryflip: {
            deps: ['jquery'],
            exports: '$'
        }
    }
});

require(['jquery', 'jqueryflip', 'AppMain'], ($, jqueryflip, main) => {
    var appMain = new main.AppMain();
    appMain.run();
});

Have fun,

– Blorkfish.

TypeScript: Implementing a Simple IOC Container for Service Location

Introduction

Inversion of Control is an object-oriented design pattern that encourages de-coupling of objects, by enforcing a layer of abstraction between object interfaces.

The purpose of this post is to explain how to implement a very simple IOC container using TypeScript, focussing on a Service Locator.  This IOC container can be used both for service location of TypeScript generated classes, and external JavaScript libraries.

Update

I have just setup a github repository for TypScriptTinyIoC : https://github.com/blorkfish/typescript-tiny-ioc

End Goal

Our end goal is to be able to use a very simple IOC for service location as follows:

// registration
var typeScriptTinyIoC = new TypeScriptTinyIOC();
TypeScriptTinyIOC.register(new TestImplementsIDrawable(), new IIDrawable());

// service location
var implementsIDrawable = TypeScriptTinyIOC.resolve(new IIDrawable());
expect(implementsIDrawable.draw()).toEqual("drawn");

Reflection (of sorts)

Unfortunately, JavaScript does not support reflection – which is a pre-requisite for IOC containers.  It does, however allow for querying an object for a specific method.  In their book, “Pro JavaScript Design Patterns”, Ross Harmes and Dustin Diaz explain how:

Consider the following code:

class Greeter {
    start() {
    }
}

class FunctionChecker {
    static implementsFunction(objectToCheck: any, functionName: string): bool {
        return objectToCheck[functionName] != undefined;
    };
}

window.onload = () => {
    var greeter = new Greeter();
    var el = document.getElementById('content');
    el.innerHTML = 'Does greeter implement start() ' +
        FunctionChecker.implementsFunction(greeter, 'start');
};

This very simple function checking algorithm will test whether the instance of greeter implements the function start.

Taking this principle one step further, lets define a TypeScript interface with a class name, and a simple array of method names,

interface IInterfaceChecker {
    className: string;
    methodNames: string[];
}

Then we can define a class that implements this InterfaceChecking interface.

export class IITodoService implements IInterfaceChecker {
    className: string = 'IITodoService';
    methodNames: string[] = ['loadMTodoArray', 'storeMTodoArray'];
};

Static Reflection

I guess that you can think of this mechanism as “static reflection”, or “manual reflection”, because we still need to define the list of method names manually.  There are benefits to this approach, though.

Interface Checking Benefits.

While this very simple mechanism may seem trivial, it’s beauty is in it’s simplicity.  It is easy to implement, and promotes reusability – because classes will have documented sets of methods, and can easily be swapped out for different classes that implement the same functionality.

It also provides us a mechanism of determining (at runtime) whether a class implements the desired functionality – and can also be invaluable when your classes depend on external libraries.  Whenever a new version is available, the library can be checked against your list of required functionality.

TypeScript Interfaces

While TypeScript provides the mechanism for strict compile-time checking of interfaces, at run-time we are still dealing with plain-old JavaScript, so the interface definitions are compiled away.  For this reason, we will define a real TypeScript interface, as well as an InterfaceChecker interface definition for use in our InterfaceChecker.  This can be easily accomplished through a simple naming standard:

A simple naming standard I-name and II-name

For standard TypeScript Interfaces, pre-fix the interface with the letter I (as per C# standards) – and for InterfaceChecker Interface definitions, prefix the class name with a double I :

This is the standard TypeScript interface for a TodoService:

interface ITodoService {
    loadMTodoArray() : any [];
    storeMTodoArray(inArray: any[]) : void;
};

and the InterfaceChecker class:

export class IITodoService implements IInterfaceChecker {
    className: string = 'IITodoService';
    methodNames: string[] = ['loadMTodoArray', 'storeMTodoArray'];
};

Then a class that implements the ITodoService :

export class ToDoService implements ITodoService {
    loadMTodoArray(): any [] {
        // load and return an array of objects
        return [{ id: 5},{ id: 6},{ id: 7}];
    };
    storeMTodoArray(inArray: any[]): void {
        // persist here
    };
}

Interface Checking

Our run-time check to ensure that the TodoService class implements ITodoService is then as follows:

var service = new TodoService();
InterfaceChecker.ensureImplements(service , new IITodoService()); 
// above will throw if not implemented

InterfaceChecker

The full code for our interface checker is as follows:

class InterfaceChecker {
    name: string;
    methods: string[];

    constructor (object: IInterfaceChecker) {
        this.name = object.className;
        this.methods = [];
        var i, len: number;
        for (i = 0, len = object.methodNames.length; i < len ; i++) {
            this.methods.push(object.methodNames[i]);
        };
    }

    static ensureImplements(object: any, targetInterface: InterfaceChecker) {
        var i, len: number;
        for (i = 0, len = targetInterface.methods.length; i < len; i++) {
            var method: string = targetInterface.methods[i];
            if (!object[method] || typeof object[method] !== 'function') {
                throw new Error("Function InterfaceChecker.ensureImplements: ' + '
                    object does not implement the " + targetInterface.name +
                    " interface. Method " + method + " was not found");
            }
        }
    };
    static implementsInterface(object: any, targetInterface: InterfaceChecker) {
        var i, len: number;
        for (i = 0, len = targetInterface.methods.length; i < len; i++) {
            var method: string = targetInterface.methods[i];
            if (!object[method] || typeof object[method] !== 'function') {
                return false;
            }
        }
        return true;
    };
}

Once we have the mechanics of our InterfaceChecker in place, it is very simple to implement an IOC container for service Location:

TypeScriptTinyIOC

class TypeScriptTinyIOC {

    static registeredClasses: any[] = [];

    static register(targetObject: any, interfaceType: IInterfaceChecker) {
        var interfaceToImplement = new InterfaceChecker(interfaceType);

        InterfaceChecker.ensureImplements(targetObject, interfaceToImplement); 
        // will throw if not implemented
        if (InterfaceChecker.implementsInterface(targetObject, 
            interfaceToImplement)) 
        {
            this.registeredClasses[interfaceType.className] = targetObject;
        }
    }

    static resolve(interfaceType: IInterfaceChecker): any {
        var resolvedInterface = this.registeredClasses[interfaceType.className];
        if (resolvedInterface == undefined)
            throw new Error("Cannot find registered class that implements " 
                + " interface: " + interfaceType.className);
        return resolvedInterface;
    }

};

TypeScriptTinyIOC usage:

The very simple IOC container can then be used as follows:

interface IDrawable {
    centerOnPoint();
    zoom();
    draw(): string;
}

class IIDrawable implements IInterfaceChecker {
    className: string = 'IIDrawable';
    methodNames: string[] = ['centerOnPoint', 'zoom', 'draw'];
}

class TestImplementsIDrawable implements IDrawable {
    centerOnPoint() {
    };
    zoom() {
    };
    draw() : string {
        return 'drawn';
    };
}

// registration
var typeScriptTinyIoC = new TypeScriptTinyIOC();
TypeScriptTinyIOC.register(new TestImplementsIDrawable(), new IIDrawable());

// service location
var implementsIDrawable = TypeScriptTinyIOC.resolve(new IIDrawable());
expect(implementsIDrawable.draw()).toEqual("drawn");

Have fun,

– Blorkfish.

In my next blog post, I will be tackling TypeScript AMD modules – understanding how to create and use them, how they help with code organisation, and how to mix standard TypeScript classes with AMD modules.

TypeScript and Backbone / Sinon Gotchas

Over the past few days I have been working through Jim Newbery’s excellent tutorial on testing Backbone applications with Jasmine and Sinon.

I just wanted to share a few gotcha’s that I experienced where TypeScript differs slightly from standard JavaScript.

1. Backbone default properties

Consider the following code:

var TodoListView = Backbone.View.extend({
    tagname: 'ul',
    className: 'todos'
});

The TypeScript equivalent would be the following:

class TodoListView extends Backbone.View {
    tagname: string = 'ul';
    className: string = 'todos';
}

Unfortunately, once the class is created, the tagname: and className: properties will be undefined.

Solution:

Set any default properties in the constructor:

class TodoListView extends Backbone.View {
    tagname: string;
    className: string;
    constructor (options?: any) {
        this.tagname = 'ul';
        this.className = 'todos';
        super(options);
    };
}

2. Backbone.Model setting attributes from default:

Consider the following JavaScript:

var Todo = Backbone.Model.extend({
    defaults: {
        'priority': 3
    }
});

In TypeScript, this should be written as:

class Todo extends Backbone.Model {
    defaults: {
        id: 0,
        priority: 0
    };
}

However, this will not compile, and generates the following error:

Expected type name

Solution :

defaults: any = {
                id: 0,
                priority: 0
        };

Note :

The official Microsoft release includes a solution that looks very convoluted :

class Todo extends Backbone.Model {
    initialize() {
        if (!this.get('id')) {
            this.set({ 'id': this.defaults().id });
        }
        if (!this.get('priority')) {
            this.set({ 'priority': this.defaults().priority });
        }

    };
    defaults() {
            return {
                id: 0,
                priority: 0,
            }
        };
}

3. Returning JSON responses with sinon.FakeServer.

Consider the following standard JavaScript code:

this.server.respondWith("GET", "/collection",
    [200, {"Content-Type": "application/json"},'{"id":123,"title":"Hollywood - Part 2"}']);

Unfortunately, this will generate an error message as follows.

Incompatible types in array literal expression

As far as I understand, this is due to mixing types in an array – which by TypeScript standards is not allowed.

Note that 200 is a number, {“Content-Type”: “application/json”} is an Object, and ‘{“id”… is a string.

Solution:

The only way that I have found around this is to drop the JSON return code and content type, and return just the string:

this.server.respondWith("GET", "/collection", JSON.stringify(this.fixture));

Have fun.

Including TypeScript comments in generated JavaScript

Just a quick one for those needing typescript comments to be included in generated JavaScript files.

The TypeScript compiler has the –c option to included comments, but this option is not available when working with Visual Studio.

The simple solution is to modify the .csproj project file and included –c in the call to the TypeScript compiler.

  • Right-click on your project file in Visual Studio, and click Unload Project.
  • Right-click again on the project file, and select Edit <your project name>.csproj

This will bring up the xml project file in edit mode.

Scroll down to the bottom of the file, and locate the following snippet:

  <Target Name="BeforeBuild">
    <Exec Command="&amp;quot;$(PROGRAMFILES)\Microsoft SDKs\TypeScript\0.8.0.0\tsc&amp;quot; @(TypeScriptCompile ->'&quot;%(fullpath)&quot;', ' ')" />
  </Target>

Now add a –c after the tsc&amp;quot; and before the @(TypeScriptCompile as follows:

From:
\TypeScript.8.0.0\tsc&quot; @(TypeScriptCompile ->

To:

\TypeScript.8.0.0\tsc&quot; -c @(TypeScriptCompile ->

Save the project file, and re-load it.

TypeScript will now include all comments within a .ts file into the generated .js file.

Have fun,

– Blorkfish

TypeScript unit testing with Visual Studio 2012, jasmine, backbone, sinon, require and testem on Windows 7

This blog is a starting point for those who are just getting into TypeScript development (like all of us I guess), and need a workable unit testing framework.

While my initial preference was to try and integrate TypeScript with Chutzpah, I ran across a few issues where changes to TypeScript files were not being picked up by the Test Discoverer.  Unit testing therefore with Chutzpah will need to wait until official support has been added.

The full solution used for this walkthrough can be downloaded from skydrive (TypeScriptSampleApp.zip) here.

UPDATE 10th October 2012:

Many thanks to Jim Newbery, over at tinnedfruit for this excellent tutorial, which has been the reference that I have been working from.  I have just updated the download to include tests from Part 1, Part 2, and Part 3.

Software Pre-requisites

This walkthrough uses the following software:

  • TypeScript : Download the installation media (for Windows) here.
  • Node.js
    • Node.js is a platform built on Chrome’s JavaScript runtime for building fast, scalable network applications.
    • It is a pre-requisite for testem, and can be downloaded here.
  • Jasmine.js :
    • Jasmine is a behaviour-driven development framework for testing JavaScript code.
    • The github wiki site is here.
  • Underscore.js:
    • Underscore.js is a pre-requisite for Backbone.js, and can be found here.
  • Backbonejs:
    • Backbone.js is a popular JavaScript library that gives structure to web applications.
    • The home page is here.
  • Sinon.js:
    • Sinon.js is a mocking and spying framework for JavaScript testing.
    • The home page is here.
  • Require.js:
    • Require.js is a JavaScript file and module loader.
    • It can be downloaded from here.
  • Testem:
    • Testem is a test runner from Toby Ho which provides an auto-re-run-on-save for test driven development.
    • Toby’s blog post on testem, including links is here.

TypeScript Setup

The TypeScript installation process is straight-forward, simply download the installation .msi from and run it.

Create a new TypeScript Project

From within Visual Studio, create a new project using the HTML Application with TypeScript template.

TypeScript_newProject

Project Structure

Testem will run unit-tests found in a particular directory, and by default it is the current directory that testem is launched from.

Unfortunately, this will cause errors when JavaScript library files and includes are in different directories.  For this reason, I have structured my project as follows:

\  (root directory)

  • This is the base directory of the project.
  • The following files should be present here:
  • testem.yml
    • this is the testem configuration file, and points to the various directories as below.
  • SpecRunner.html
    • This is the standard Jasmine SpecRunner.html file from the source, and has been modified as per the various directories below.
    • Note that I have included this file for manual debugging purposes.

\lib

  • the lib directory will contain all required JavaScript library downloads ( except for jasmine ).
  • I have had problems with the Testem runner if the jasmine.js files are in this directory.
  • the following files should be present in this directory:
    • backbone.js
    • require.js
    • sinon-1.4.2.js
    • underscore.js

\lib\jasmine

  • this folder will house the jasmine.js downloads – and are used only for manual de-bugging purposes.
  • As mentioned before, having these files in the parent \lib directory will cause problems with testem.

\test

  • this folder contains TypeScript testing files

\tests\sub_folder

  • This folder contains more TypeScript testing files – it is here simply to test that Testem can traverse sub-folders when looking for tests.

\modules

  • This folder contains the TypeScript .d.ts modules that are referenced in the tests.
  • As your project grows, you will be updating the module files to provide better type safety for external JavaScript libraries
  • The following files should be present in this directory:
    • Backbone.d.ts
    • Jasmine.d.ts
    • require.d.ts
    • sinon.d.ts

A screenshot of the Directory structure from Visual Studio is as follows:

TypeScript_VisualStudio_snapshot

Running Testem

Once testem is installed, simply fire up a command prompt, and change to the root directory of your project.

Before we run testem, lets have a look a the testem.yml file a the base of the project directory:

framework: jasmine
src_files:
- lib/require.js
- tests/**/*.js
  • Note that we are using jasmine as the testing framework.
  • src_files points to the directories in which to find tests, as well as javascript files that are required for each run.
    • by using lib/require.js, we can inject further required javascript files into our tests as dependencies.
    • tests/**/*.js specifies that all javascript files in the tests directory, and any sub-directories will be scanned for tests.
Fire up testem by running testem on the command line.

You should see testem start, and then show “Waiting for runners…”

TypeScript_testem_1

Now fire up a browser, and type in the url shown in the top-left-hand corner of testem.  In this case, it’s http://localhost:7357

Congratulations, your unit tests should now run.

TypeScript_testem_2

And the testem runner:

TypeScript_testem_3

Anatomy of a test

Lets have a quick look at the code of a sample test.

Firstly, we need to include references to our modules in order for TypeScript to allow us to compile our tests:

/// <reference path="../modules/Jasmine.d.ts" />
/// <reference path="../modules/require.d.ts" />
/// <reference path="../modules/Backbone.d.ts" />

TypeScript Modules

As your project matures, and the list of functions you are using extends, you will need to update these module files with function signatures that match any function or property that you are accessing from and external library.

If we look at Jasmine.d.ts, we will notice that it has function signatures that cover the describe, it, expect, beforeEach and afterEach functions:

// module for Jasmine

declare module describe {
    export function(testDescription: string, f: Function) : any ;
}

declare module it {
    export function(testDescription: string, f: Function) : any ;
}

declare module expect {
    export function (actual: any): any;
}

declare module beforeEach {
    export function (f: Function): any;
}

declare module afterEach {
    export function (f: Function): any;
}

The test itself:

Lets now have a look at the code for a standard jasmine unit test:

describe("Jasmine_ModelTests:", function () {
    it("should pass a simple test", function () {
        expect("test").toEqual("test");
    });
});

Pretty self-explanatory.

Using require.js in tests that target further frameworks:

The following test is using backbone.js as a framework, and therefore needs to include underscore.js and backbone.js as test dependencies:

require(["lib/underscore.js","lib/backbone.js"], function () {
    describe("Jasmine_ModelTests_with_require:", function () {
        it("should pass a simple test with backbone", function () {
            var model = new Backbone.Model({
                name: "myName",
                title: "myTitle"
            });

            expect(model.get("name")).toEqual("myName");
            expect(model.get("title")).toEqual("myTitle");
        });
    });
});

Note here the first line of the test is a call to require to load both underscore.js and backbone.js before the test begins.

TypeScript require function call structure

One important thing to note here is the structure of the function call.
According to require.js documentation, the function call should be as follows (outlined in red):

require(["lib/underscore.js", "lib/backbone.js"], function (underscore, backbone) {
    // underscore and backbone are now fully fledged namespaces
});

Note that this convention will allow code within the require block to use underscore as a namespace, and backbone as a name space as follows:

var model = new backbone.Model({
                name: "myName",
                title: "myTitle"
            });

Unfortunately, this will break TypeScript’s auto-completion and Intellisense, as it cannot find a reference to backbone in any of the module definitions.

For this reason, it is better for TypeScript to use the function call without specifying namespaces, as follows:

require(["lib/underscore.js","lib/backbone.js"], function () {

});

Finally

The purpose of this blog has just been to get an automated unit testing framework running with TypeScript.
My understanding of the TypeScript language annotations, as well their use – will grow in time, so I would welcome any comments or suggestions that you have in this regard.

Have fun,
– Blorkfish.