Unit testing TypeScript with Alsatian
Oct 15, 2016
TypeScript is a superset of JavaScript which allows developers to use static typing in their projects. It runs through a "transpiler" (unlike a compiler which turns source code into a lower-level language, a transpiler turns source code into a language at the same level) which turns it into plain old JavaScript.
There are a number of reasons to use TypeScript: it checks your code syntax when compiling so that you know of any syntax errors immediately, it allows for interfaces and abstract classes, it lets you have private/public variables. However it would be something of a waste to set up a TypeScript environment to check your code is syntactically correct, if you weren't going to also unit test it to check it is correct at runtime.
Alsatian is a unit testing framework written in TypeScript, for TypeScript. It follows good programming conventions to make your program SOLID (read Uncle Bob's post on the SOLID principles if you're not familiar with them):
- no global variables: import what you need from Alsatian and use it as required.
- small, simple tests: Alsatian lets you use test cases unlike other JavaScript test frameworks, so you can write a test and execute it with lots of different data sets without rewriting the test itself.
- gives TAP-compliant output: the Test Anything Protocol is a specification which allows test frameworks to report their results in a consistent manner, so that they can be easily interpreted. Read more about TAP on The Effective Perler.
- gives clear diagnostic information: if your tests aren't set up right, Alsatian gives clear, informative diagnostics so you can fix them stress-free.
- tested and bootstrapped: Alsatian is fully TDDed using Alsatian, so you know it works!
- it's TypeScript!: Alsatian is written with TypeScript in mind and takes advantage of this with cool features such as decorators to make your tests even neater.
tl;dr
The full code example used in this tutorial can be found on GitHub at jameskmonger/unit-testing-typescript-alsatian.
Installation
Let's get set up. You will need typescript
and alsatian
installed for this. You can get both of them from npm.
$ npm install typescript alsatian --save-dev
They're both installed as developer dependencies because they're not needed in the final bundled code.
Structure Setup
Normally, I put my tests in a folder called test
, with the test files called feature.test.ts
, where of course feature
is the name of the feature that we're testing.
I also like to rely on local dependencies rather than global ones, so let's set up an npm script which is a proxy for the alsatian
command. Go into package.json
, find scripts
and add this:
"test": "alsatian \"./test/*.test.js\""
That will let you run npm test
in order to run your tests.
Writing your first test
Let's make a function which takes in two numbers and adds them. Fairly simple, I know, but the function itself isn't what this article is about.
./src/add.ts
:
export default (a: number, b: number) => {
throw new Error("unimplemented");
};
Now let's create the test file for this at ./test/add.test.ts
. Start by importing Test
, TestCase
and Expect
from "alsatian"
, as well as importing our default export from add.ts
.
import { Test } from "alsatian";
import add from "../src/add";
export class AddTests {
}
Notice how we export AddTests
- Alsatian works by getting all exported members from a test file and seeing if they're test fixtures.
Let's first test that the function doesn't throw an error when given two numbers. Start by making a public function which is annotated with @Test()
.
@Test()
public shouldNotThrowError() {
}
Now we add add an Expect
statement. This is similar to expect
in Jasmine and assert
in QUnit and mocha. We want to assert that when we call add
with two numbers, it doesn't throw an error. Luckily, with Alsatian, it's nice and simple!
Expect(() => add(1, 2)).not.toThrow();
Build the project and run it with npm test
and you'll see that it fails. Good! That means that our test is working. Let's fix add
so it doesn't throw an error.
export default (a: number, b: number) => {
};
Test cases
Now, we can't be entirely sure that our test works in all situations - we're only testing it with one set of variables. In other JavaScript unit testing frameworks, it could be quite difficult to do this - a lot of them would have you just write the whole test again for each set of data. Alsatian provides the TestCase
attribute to make this much easier.
First, we need to add arguments to our test function:
public shouldNotThrowError(a: number, b: number) {
And we need to use these arguments in our add
call:
Expect(() => add(a, b)).not.toThrow();
Lastly, we need to switch out the Test
annotation for multiple TestCase
annotations with their own values for each.
@TestCase(1, 2)
@TestCase(5, 10)
@TestCase(8, 3)
public shouldNotThrow(a: number, b: number) {
Expect(() => add(a, b)).not.toThrow();
}
This will now run the test three times - once with 1, 2
, once with 5, 10
and once with 8, 3
.
Testing the functionality
The TestCase
annotation takes as many arguments as you can throw at it, which is good because it means you can pass in the test variables and the expected result! Let's give it a try with a new test that makes sure the numbers add correctly.
@Test()
public shouldAddCorrectly() {
Expect(add(1, 2)).toBe(3);
}
Simple enough - now let's change it so that there's a number of test cases for it.
@TestCase(1, 2, 3)
@TestCase(5, 5, 10)
@TestCase(7, 12, 19)
public shouldAddCorrectly(a: number, b: number, expected: number) {
Expect(add(a, b)).toBe(expected);
}
Run the test now nad you'll see that they'll all fail, so let's update the implementation so that they all pass:
export default (a: number, b: number) => {
return a + b;
};
Asynchronous tests
One thing that Alsatian does really well is asynchronous tests. You simply use the AsyncTest
annotation and make your test fixture return a Promise
which is resovled when the test is complete.
@AsyncTest()
public shouldLayHere() {
wasteTime();
return new Promise((resolve, reject) => {
chaseCars()
.then(cars => {
Expect(cars.length).toBe(5);
resolve();
});
});
}
Simple! Do your actions, return a promise and resolve the promise when it's complete!
Conclusion
Alsatian is a really neat way to test your TypeScript code. It's been written with TypeScript in mind so there's no clunkiness with it, and no coding practices that are okay in JavaScript and bad in TypeScript.
It's bundled with a nice reporter, tap-bark
, however if you want to use another TAP reporter of your choice, you can pass the --tap
argument to Alsatian and then pipe it into your reporter.
Alsatian is currently approaching beta (you can track the progress on the GitHub milestone tracker), so while it's been pretty intensively tested and I've used it in a number of personal projects, there's the chance that there could be issues with it. If you do encounter an issue, please don't hesitate to log it on the GitHub repo.
If you want to see the full code example from this tutorial, you can see it on GitHub at jameskmonger/unit-testing-typescript-alsatian.