Unit testing node applications with TypeScript — using mocha and chai

TypeScript has gotten so much better in the last year, and so many projects have adopted it. But how do you write your tests in TypeScript? TypeScript has gotten so much better in the last year, and so many new applications and projects are built on top its awesomeness. But how do you write your te

Unit testing node applications with TypeScript — using mocha and chai
That’s how awesome I feel when I write tests in TypeScript

TypeScript has gotten so much better in the last year, and so many new applications and projects are built on top its awesomeness. Its first-class integration and support in Visual Studio Code helps a lot, and I think TypeScript as a language is here to stay, despite some people having their misgivings.

The usual process is to write code in TypeScript, and have it compiled to JavaScript, and then served to the browser as a JavaScript file. We usually only have to test the JavaScript files that are emitted by the TypeScript compiler. But wouldn’t it be so much easier just to use TypeScript to do our unit tests? We could use the same syntax, we wouldn’t have to worry about having JavaScript sources for tests, and TypeScript sources for the app. It’s just so much better.

But, how do we do it?

Installing the development dependencies

A few simple tools to get us started — mocha, chai, and ts-node

I have looked around, and I’ve found that mocha is a good fit as a testing framework, and chai as an assertion library. I like the expect style of assertions more, and I like that chai has it. These are just 2 tools, and we can probably explore and find more tools. But these are simple and get the job done, and work with TypeScript.

As of writing, Ava (which is a framework I really like for its parallel testing capabilities) doesn’t have first-class TypeScript support. And I don’t like the idea of using yet another transpiler like Babel when I already have the TypeScript compiler. So, let’s start with mocha and chai.

The node package manager, fondly called npm, has had its own share of improvements. The version 5 makes it unnecessary to use a package manager like yarn, so we can stick to it. We need to install a couple of things, and I’ll explain them as we go.

The additional packages are type definitions for mocha and chai, and ts-node, a TypeScript execution environment for node. We will not be needing type definitions for ts-node, as it is not a package we will import and use in our TypeScript files.

Writing our first TypeScript test

It’s really easy — and not much different from how we write tests otherwise

For the sake of writing our first test, let’s write some code:

Now that we have a function to test, let’s write our first TypeScript test:

Running our unit tests

It’s as simple as calling mocha now as usual, with ts-node

Remember how we use mocha to run our tests? We create an npm script that calls mocha with the path as a parameter. That’s the same thing we’re about to do. The only change is that instead of letting node run mocha, we’re gonna register ts-node to run mocha.

Here’s a sample output:

Quirks with client side applications

A few kinks to iron out when testing client side applications

Recently when I wanted to test a client side application, it didn’t go so smoothly. I was using webpack — which is an amazing module bundler, by the way — to import styles into files which declared components, and I found out that I could no longer unit test those components with what I’ve described above.

Here’s an example. Let’s add a styles.scss file to the mix:

Now, the file to be tested imports a stylesheet. This will be processed by webpack when it is used to compile static assets and does its work as a module bundler. It has to be in the code. I cannot separate this out into another file. But if it exists, ts-node cannot import the stylesheet as it isn’t a valid TypeScript file. We run into an error if we try to run the test script.

Time to add another package to solve the problem. And it’s very aptly named:

And, we need to update our test script:

Now, our tests pass:

If we reference window, or the DOM, in our code (client side application, right?), that opens up new errors for us, because ts-node does not know about the window or the DOM. And it’s very common to use them, and so it’s easy to run into these issues (as I found out).

This time, we just try to reference window:

This is a simple way to introduce an error in our tests for referencing window. Do NOT do this in your app.

Now, when we try to run our tests:

Boom! Okay, so let’s fix this. It’s as you expect — more packages. This time, we need 2:

And, we need to update our test script:

Here’s our output:

Great, our tests are passing again.

You’d think that was all. But there’s more. But this is very specific, so you might not need it. This is only required if you’re using webpack’s code splitting feature with TypeScript. You can skip ahead to the next heading if this doesn’t apply to you.

While webpack allows code splitting via dynamic imports with System.import(), TypeScript needs some configuration in order to output the right JavaScript that webpack can process as dynamic import for code splitting. In order to get it right, you need to change your tsconfig.json slightly. Typically, your tsconfig.json looks like this, when you need code splitting in webpack:

The important parts are the module and moduleResolution. The value for module is usually left as commonjs, but for code splitting to work, we need to change it to esnext. And this will cause problems for our unit tests. To fix this, we need to change the module back to commonjs when running mocha with ts-node. I recommend using cross-env to do this, as it provides for cross-platform support.

Time to install a package:

We change our test script to provide the correct value for module for ts-node and mocha to work their magic with our TypeScript tests:

We fix the module by setting an environment variable called TS_NODE_COMPILER_OPTIONS, and things start working again.


Writing more tests in TypeScript

Well, we’re there now, and it’s all over. We just need to keep adding tests now.

It’s been a long post, but I hope you found it useful. And I hope you write your tests in TypeScript henceforth.

What do you think?

Did this work for you?
Does having type definitions in your tests help?
Could I have done something better?
Have I missed something?

Please share your thoughts using comments on this post. Also let me know if there are particular things that you would enjoy reading further.

Cheers!

Subscribe to The ArtfulDev Journal

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe