Writing unit tests in TypeScript (by the example of cats)
How do I write unit tests in a TypeScript project? In this article, we will try to answer this question and also show you how to create a unit testing environment for projects using TypeScript.
What are unit tests?
Unit tests – Tests applied across different layers of an application that test the smallest divisible logic in an application: for example, a module, class, or method.
The essence of unit tests is to write them to each new class or method, checking if the next change in the code has led to the appearance of errors (bugs) in the already tested parts of the program.
It follows that unit tests should be fast. Such tests can be performed frequently during programming. That is, after writing a new class or method, the developer writes a package of tests for them, then runs them along with the existing tests of the rest of the program. At the output, we get the code covered with tests, which allows us to avoid bugs already at the start of development.
Setting up the environment
So now to the point. Suppose we have a certain project with the following structure:
1 2 3 4 5 |
project | node_modules | src | package.json | tsconfig.json |
In ./src
there is a cat.module.ts
module that contains a simple Cat class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
export class Cat { public name: string; public color: string; constructor(name: string, color: string) { this.name = name; this.color = color; } public move(distanceMeter: number) : string { return `${this.name} moved ${distanceMeter}m.`; } public say() : string { return `Cat ${this.name} says meow`; } } |
As you can see, our class contains a constructor that accepts name and color values as well as a couple of methods. This class will be our test object (SUT – a system under test).
Let’s create a test folder at the root of the project, in which we will store our tests.
Next, install the required npm packages:
npm install –save-dev ts-node mocha @testdeck/mocha nyc chai @types/chai
Brief description of packages:ts-node
– a package for executing TypeScript and REPL in the node.js environment.
mocha
is a popular, flexible test framework that allows you to develop tests of any level. We will use it as the basis for our tests. Together with it, we use @testdeck/mocha
– the implementation of the testdeck decorator for Mocha to write our tests in an OOP style.
nyc
is a modern CLI of the popular Istanbul utility that calculates current test coverage of code.
chai
is a popular library for checking assertions, suitable for many testing frameworks. We will use it in tandem with Mokka. Let’s also add @types/chai
so that our teas can work freely with typescript types
After installing all the necessary packages, we will create a tsconfig.json
file in our test
folder, in which we will add TS configs which will be called separately for our tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
{ "extends": "../tsconfig.json", "compilerOptions": { "baseUrl": "./", "module": "commonjs", "experimentalDecorators": true, "strictPropertyInitialization": false, "isolatedModules": false, "strict": false, "noImplicitAny": false, "typeRoots" : [ "../node_modules/@types" ] }, "exclude": [ "../node_modules" ], "include": [ "./**/*.ts" ] } |
In the include line, we specify to include all files in the test folder with the extension .ts
Then we will create a file register.js
in the root of the project, in which we will describe the instructions for ts-node, from where to run and transpile our tests.
1 2 3 4 5 6 7 8 |
const tsNode = require('ts-node'); const testTSConfig = require('./test/tsconfig.json'); tsNode.register({ files: true, transpileOnly: true, project: './test/tsconfig.json' }); |
Next, create a .mocharc.json
file there at the root, with the following content:
1 2 3 4 |
{ "require": "./register.js", "reporter": "list" } |
And the .nyrc.json
file with the config of our utility for analyzing test coverage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "extends": "@istanbuljs/nyc-config-typescript", "include": [ "src/**/*.ts" ], "exclude": [ "node_modules/" ], "extension": [ ".ts" ], "reporter": [ "text-summary", "html" ], "report-dir": "./coverage" } |
After adding all the necessary configs, our project tree will look like this:
1 2 3 4 5 6 7 8 9 10 |
project | node_modules | src | test | --- tsconfig.json | .mocharc.json | .nyrc.json | package.json | register.js | tsconfig.json |
Now you need to add the startup script to package.json
1 |
"test": "nyc ./node_modules/.bin/_mocha 'test/**/*.test.ts'" |
We have finished with the setup, now we can write the first test.
Writing tests
Create a file in the ./test
folder cat.unit.test.ts
file and write the following code in it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import { Cat } from '../src/cat.module'; import { suite, test } from '@testdeck/mocha'; import * as _chai from 'chai'; import { expect } from 'chai'; _chai.should(); _chai.expect; @suite class CatModuleTest { private SUT: Cat; private name: string; private color: string; before() { this.name = 'Tom'; this.color = 'black'; this.SUT = new Cat(this.name, this.color); } } |
We have now imported our Cat class from the cat.module.ts
module added the imports of the required test libraries and created the necessary variables to initialize our class.
In the before section, we set the parameters necessary for the class and created an instance of the Cat class on which the tests will be passed.
Now let’s add the first test that checks that our cat exists and has the name that we gave it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import { Cat } from '../src/cat.module'; import { suite, test } from '@testdeck/mocha'; import * as _chai from 'chai'; import { expect } from 'chai'; _chai.should(); _chai.expect; @suite class CatModuleTest { private SUT: Cat; private name: string; private color: string; before() { this.name = 'Tom'; this.color = 'black'; this.SUT = new Cat(this.name, this.color); } @test 'Cat is created' () { this.SUT.name.should.to.not.be.undefined.and.have.property('name').equal('Tom'); } } |
Run the test with the npm test command and get something like this in the console:
As you can see, our coverage is not complete, lines 11-15 remained untested. These are the move
and say
methods of the Cat class.
We add two more tests for these methods and we end up with such a file with tests:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import { Cat } from '../src/cat.module'; import { suite, test } from '@testdeck/mocha'; import * as _chai from 'chai'; import { expect } from 'chai'; _chai.should(); _chai.expect; @suite class CatModuleTest { private SUT: Cat; private name: string; private color: string; before() { this.name = 'Tom'; this.color = 'black'; this.SUT = new Cat(this.name, this.color); } @test 'Cat is created' () { this.SUT.name.should.to.not.be.undefined.and.have.property('name').equal('Tom'); } @test 'Cat move 10m' () { let catMove = this.SUT.move(10); expect(catMove).to.be.equal('Tom moved 10m.'); } @test 'Cat say meow' () { expect(this.SUT.say()).to.be.equal('Cat Tom says meow'); } } |
Run our tests again and see that the Cat class now has full test coverage.
Outcome
As a result, we have created a test infrastructure for our application and now we can cover any new module or class with tests, checking that the existing code has not broken.
Related Posts
Leave a Reply Cancel reply
Service
Categories
- DEVELOPMENT (103)
- DEVOPS (53)
- FRAMEWORKS (26)
- IT (25)
- QA (14)
- SECURITY (13)
- SOFTWARE (13)
- UI/UX (6)
- Uncategorized (8)