Test Driven Development with TypeScript

Test Driven Development using TypeScript and QUnit.

In this article, I will demonstrate how to write testable code using TypeScript. I assume that the reader is familiar with JavaScript, heard about Typescript and has done or secretly desires to do test driven development.

There is an excellent quick start guide on Typescript that I recommend you to go through before moving ahead.

Project Setup

I am writing the code in Visual Studio 2015 Update 2 (which already has the necessary ingredients to author TypeScript applications). You can also use Visual Studio Code for authoring web applications with TypeScript.

1. Start Visual Studio and Create a new project. From the installed templates, select Html Application With TypeScript template.

Project_template

2. Notice how Visual Studio has done the required scaffolding for your first TypeScript application. In fact, if you press F5 on your keyboard, you will be able to run this application in your browser. Take some time here and see how closely TypeScript resembles an object oriented programming language. You’d see a class, a constructor method etc.

3. When you are done with admiring the beauty of TypeScript, you may select the code in app.ts and delete it. Since we are doing Test Driven Development, we will not be writing any Production code without accompanying tests!

4. In an enterprise project, you’d usually create a new Test Project in the solution. But for the purpose of this article and also to keep things simple in the beginning, I will just create a new folder called as “Tests” under the same project and add a test file in it. .

The goal of the application is to calculate area of different shapes. I usually choose simple applications and simple domains to demonstrate new stuff. This helps in focusing more on the subject rather than focusing on details of the domain.

At this point, I have renamed app.ts to Rectangle.ts and the test file name is RectangleTests.ts

image

Set up for writing Tests

To test TypeScript code, you’d need a testing framework. There are few popular frameworks available like Jasmine and QUnit. For the purpose of this article and also because at the time of writing this article, I couldn’t find any example where typescript is used with QUnit and Chutzpah (more on that later), I decided to write this article using QUnit.

Go ahead and add Qunit-MVC via the package manager console.

   1: Install-Package Qunit-MVC

You’d also need a type definition file for Qunit. The purpose of type definition file (file with extension *.d.ts) is to let TypeScript know about the types that exist in an API. There is a github project called as Definitely Typed,  which provides high quality type definitions for almost all JavaScript APIs.

Run below command to add the type definition file for QUnit.

Install-Package qunit.TypeScript.DefinitelyTyped

Apart from that, you’d need a test runner, where you can see your passing/failing tests. For that I am going to use Chutzpah. To use Chutzpah in Visual studio you need to add an extension. Select Tools>Extensions and Updates and search for Chutzpah. (Since I already have Chutzpah extension added in my VS, I see the option to disable or uninstall it.)

image

Add chutzpah.json file in the root of your project. This json file provides a range of configuration for Chutzpah test runner. For the sake of simplicity, I am configuring the json file with very basic settings:

{
"Compile": {
"Mode": "External",
"Extensions": [ ".ts" ],
"ExtensionsWithNoOutput": [ ".d.ts" ]
}
}

Compile Mode External denotes that Chutzpah test runner can expect compiled .JS file in the default output directory. If you select mode as Executable then you need to provide a powershell or bat file which will convert .ts files to .js file. In this project, I will use tsconfig.json file and configure it in such a way that visual studio will take care of generating a .js file while building (ctrl + shift + b) the project.

Add a file with name tsconfig.json in the root of the project with below settings.

{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true
}

With that in place, we are all set to write the tests.

The Red Green Refactor Cycle

Open RectangleTests.ts file and write you first failing test. Below is my first failing test.

/// <reference path="../Scripts/typings/qunit/qunit.d.ts" />
/// <reference path="../Rectangle.ts" />

QUnit.module("RectangleTests.ts Tests");

test("Can create instance of rectangle class with specified length and breadth", () => {

var rectangle = new Rectangle();

ok(rectangle != undefined,
"Assert that rectangle class can be instantiated");

});

There is no need to run tests. The project will not compile because I have not created the Rectangle class yet. A non-compiling class is equivalent to a failing test. So lets write just enough code to make this test pass.

Open Rectangle.ts file and type below code:

class Rectangle {

}

Perfect! This is exactly what we need to make the failing test pass. Lets run the test and confirm if our test is passing.

image

So that’s our Red and Green cycle in TDD. But, what about Refactor? As of now, I do not see any need to refactor the code.

Let’s write the next test. I would like the length and breadth of the rectangle to be passed as constructor argument to the Rectangle class, so lets add another test which passes length and breadth to the Rectangle class.

You’d notice that the project is not compiling because there is no constructor in the Rectangle class which accepts two arguments. To make the project compile, lets add a constructor to the Rectangle class:

class Rectangle {
private breadth: number;
private length: number;

constructor(length: number, breadth: number) {
this.breadth = breadth;
this.length = length;
}
}

Now lets run the tests again.

But hey! Our first test is now failing! Because we do not have any blank constructor in the Rectangle class. At this point we need to do some refactoring!

Do we really need the first test? I would say No, because with the new constructor, we are enforcing a contract that to calculate area of a Rectangle you need to provide length and breadth of the rectangle before hand. So lets delete the first test.

During the refactoring cycle, not only the production code, but the test code can and should be refactored.

Tests deserve to be maintained to the same level of quality as the production code. Indeed, perhaps the tests deserve even more attention than the production code since the quality of the production code depends on the tests;

-Bob Martin

At this point, I would skip demonstrating all the Red Green and refactor cycles that I went through to add the function which calculates the area of a rectangle. The reason I want to skip that is because I want to demonstrate an interesting concept called as “Stubbing”!

Stubbing

For those who are familiar with test driven development would know that unit tests without the ability to stub external dependencies are not of much use. To demonstrate stubbing in our example, I am introducing an external dependency, a webservice which helps in calculating the area of a triangle.

Area of Triangle = 1/2 (height * base)

In our hypothetical world, there exists a service, which if called, provides the height of the triangle. The requirement is that we call a web service to get the height value.

To enable stubbing, we need to add typeMoq framework to the project. Using typeMoq, we can stub external dependencies and also use mocking to verify expectations on method calls.

Install-Package typemoq

Let’s create a class with name TriangleTests.ts and write the first failing test.

/// <reference path="../Scripts/typings/qunit/qunit.d.ts" />
/// <reference path="../Scripts/typemoq.d.ts"/>
/// <reference path="../Triangle.ts"/>

QUnit.module("Triangle test with external dependency");

test("Can create instance of Traingle class",
() => {
var triangle = new Triangle(baseValue);

ok(triangle != undefined, "Assert that the triangle instance is not undefined");
});

This class fails to compile, so lets quickly add Triangle.ts class with a constructor accepting base value:

class Triangle {
private base: number;

constructor(base: number) {
this.base = base;
this.ajaxWrapper = ajaxWrapper;
}

Now, the tests should pass. Next, we need to somehow call a service from this class to get height of the triangle. This means, we need to add a new class which does the Ajax call to a hypothetical service. With that information, lets write our next test.

test("Can create instance of Triangle class with an external dependency",
() => {
var triangle = new Triangle(baseValue, mock.object);

ok(triangle != undefined, "Assert that the triangle instance is not undefined");
});

Notice the usage of mock.object. At this point the project will not compile, so lets fix it and make the tests run. Add an interface in Triangle.ts file.

interface IAjaxWrapper {
        performAjaxCall(): number;
}

and an empty class which implements this interface

class AjaxWrapper implements IAjaxWrapper {
performAjaxCall(): number { throw new Error("Not implemented"); }
}

Notice that we are not writing any implementation for this class. In fact, any attempt to invoke the performAjaxCall method on this class will throw an exception. At this point, we just want our test to pass which is using the mock object to stub an external dependency.

Lets modify the Triangle class and add a new constructor argument:

class Triangle {
private ajaxWrapper: IAjaxWrapper;
private base: number;

constructor(base: number, ajaxWrapper: IAjaxWrapper) {
this.base = base;
this.ajaxWrapper = ajaxWrapper;
}

Notice the usage of IAjaxWrapper interface in the constructor argument, just like how its done in any object oriented language. Add below references in the test class. With these references, we are telling Chutzpah test runner where to find the typemoq library.

/// <chutzpah_reference path="../Scripts/typemoq.js"/>
/// <chutzpah_reference path="../Scripts/underscore.js"/>
/// <chutzpah_reference path="../Scripts/typings/qunit/qunit.js"/>
/// <reference path="../Scripts/typemoq.d.ts"/>

With typemoq referenced in the test class, lets define our mock object.

let mock: TypeMoq.Mock<IAjaxWrapper> = TypeMoq.Mock.ofType<IAjaxWrapper>(AjaxWrapper);

Below is the complete code:

/// <reference path="../Scripts/typings/qunit/qunit.d.ts" />
/// <reference path="../Scripts/typemoq.d.ts"/>
/// <reference path="../Triangle.ts"/>
/// <chutzpah_reference path="../Scripts/typemoq.js"/>
/// <chutzpah_reference path="../Scripts/underscore.js"/>
/// <chutzpah_reference path="../Scripts/typings/qunit/qunit.js"/>

let mock: TypeMoq.Mock<IAjaxWrapper> = TypeMoq.Mock.ofType<IAjaxWrapper>(AjaxWrapper);

QUnit.module("Triangle test with external dependency");

test("Can create instance of Triangle class with an external dependency",
() => {
var triangle = new Triangle(baseValue, mock.object);

ok(triangle != undefined, "Assert that the triangle instance is not undefined");
});

This test will pass!

Let’s write the next test to actually calculate the area of triangle.

let baseValue = 2;

test("Can calculate area of a triangle",
() => {
var heightValue= 10;
mock.setup(x => x.performAjaxCall()).returns((n: number) => heightValue);
var triangle = new Triangle(baseValue, mock.object);

var result = triangle.calculateArea();

ok(result === 10, "Assert that area is calculated correctly.");
});

Notice how the performAjaxCall method is being set up to return a predefined height value. At this point, we do not care how AjaxWrapper is going to fetch the value of triangle’s height, all we know is that the performAjaxCall method is setup to provide that value. Notice we just need an interface, so that we can handle the dependency with fake implementation and move ahead with our tests.

Of course, this test will fail, because calculateArea method does not exist. Let’s fix that and make the test pass. Create a calculateArea method in the Triangle.ts class. Below is the code:

class Triangle {
private ajaxWrapper: IAjaxWrapper;
private base: number;

constructor(base: number, ajaxWrapper: IAjaxWrapper) {
this.base = base;
this.ajaxWrapper = ajaxWrapper;
}

calculateArea(): number {
var height = this.ajaxWrapper.performAjaxCall();
return (1/2) * this.base * height;
}
}

This should be enough for the tests to pass.

With that, I’d conclude this article. Hope the article was helpful.

Quick Tips

1. Chutzpah can show test results on a web page. To view your tests in a browser, right click on the test file and from the context menu Select Run Chutzpah with > Debugger. You can also put breakpoints and debug your tests.

image

2. You can check the code coverage of your TypeScript code. Below is the code coverage of TriangleTests.ts

image

The code for this demonstration can be found at my github repository.