Saturday, May 7, 2016

Testing TypeScript Modules with Jasmine

In my previous post I talked about using TypeScript/ES6 modules. I have been thinking about making the transition from namespaces to modules, but there were a few things I needed to work out first. First of all how to load them (native ES6 support isn't widely available at the time of this writing). In the previous post I talked about using a library called Require1k (http://requirejs.org/) as the module loader. As the name suggests it is only 1kb in size yet provides the basics you need for CommonJS module loading. So far it does everything I need.

The next thing I needed to work out was how to package up my application when it was ready for release. In that post I also talked about using WebPack in Node.js to combine and minify the application modules.

The final thing I needed to work out was how to get my Jasmine (http://jasmine.github.io/) unit tests to run against modules. This was giving me a lot of trouble; it's not as easy as you might think. Jasmine was built in such a way that it expects all of your files to be loaded using script tags, then it runs when the page onload event fires. Unfortunately when using a module loader things don't happen that way. The module loader doesn't load your modules until after the page is loaded.

So for the remainder of this post I'll show how I got my Jasmine tests to run using the Require1k module loader. Although I'm using a specific module loader there shouldn't be any problem using the same principles for any other CommonJS module loader library as they most all work the same way.

Creating the Unit Tests


The first thing we need to do is create unit tests. I won't go into how to write Jasmine tests. There are plenty of good examples on their web site. The only difference is that you will need to use import statements to reference the modules you want to test.

For example, say you want to test module1.ts. Then your unit tests might look something like this.

/// <reference path="../scripts/typings/jasmine/jasmine.d.ts" />
import * as Module1 from "./module1";
describe("When testing module1", () =>
{
    it("should be true when something happens", () =>
        expect(Module1.something()).toBeTruthy());
});

First we have a reference to the Jasmine type definition file (because as TS programmers we like to have better tooling and autocompletion). Then we import everything from module1. Then we define the Jasmine specs for the unit tests. It's that simple ;-).

One important thing to point out here: your unit tests are modules too. Once you decide to use modules everything needs to be a module. Our unit tests need to load the modules they test, therefore they have to be modules for the module loader to pick up the imports and load them.

The Bootstrap File


Now we need a bootstrap file. This is the main file that gets things going. We will call this file unit-tests.ts.

declare function require(s: string): void;
 
require("./module1.spec");
require("./module2.spec");
 
jasmine.getEnv().execute();

The first thing we did was to declare the require function. This is the function defined by the Require1k library (or any other library for loading CommonJS modules) to load a module file. Without this we would get errors from the compiler because it doesn't know what "require" is.

Next we use require to load all of the unit test modules. In this case we have unit tests for two different modules.

I like to name my unit test files with the name of the module it's testing and a ".spec.ts" extension. This way you know exactly which module it's for and you know it's a Jasmine test file (Note: unit tests are known as specs in Jasmine).

The last line is necessary to tell Jasmine to start running the tests. As I stated above, Jasmine will look for any unit tests and run them on page load. But since this is all happening after page load we have to explicitly tell it to run again.

You might be asking yourself: why not use import statements to load all of the unit test files? That's a valid question. The unit tests are modules, I said so previously. The answer is: because nothing is being exported from the unit test modules. All they do is define Jasmine specs. Therefore if you tried to import them the TypeScript compiler would see there's nothing being exported and just skip it. Then your unit tests wouldn't load and you would be wondering what's going on (not that I'm speaking from experience or anything like that).

The Host File


The final thing we need to do is set up the host HTML file to run the unit tests. It will have all of the files for jasmine, of course. Then it will need the script tag for Require1k.

<link href="../Content/jasmine/jasmine.css" rel="stylesheet" />
<script src="../Scripts/jasmine/jasmine.js"></script>
<script src="../Scripts/jasmine/jasmine-html.js"></script>
<script src="../Scripts/jasmine/boot.js"></script>
 
<script src="require1k.js" data-main="./unit-tests"></script>

The "data-main" attribute tells Require1k what the main or start file is. In this case unit-tests.js which we created above. This will define the starting point of the module loader. From there it will load the unit test modules which will in turn load the modules they are testing.

Conclusion


The trick to testing external modules with Jasmine is to have a single bootstrap file that loads all of the unit test files and to tell the module loader where to find it. Then the key to making it work is to tell Jasmine to run again after all of the tests and modules have been loaded.

Switching from internal namespaces to external modules in TypeScript requires a whole new way of looking at things and some new ways of doing things that you're not used to. You just have to learn a few new tricks to get it to work across the board from your source code to your unit tests.

I think in the end using modules is the way of the future for TypeScript, and JavaScript. Once ES6 is fully supported by all browsers and able to provide native module loading things will be a lot easier and modular JS will become more popular.

<jmg/>