Skip to content

Instantly share code, notes, and snippets.

@stuft2
Last active March 26, 2021 19:13
Show Gist options
  • Save stuft2/ce40d16f315812533ceca5c988d69dbe to your computer and use it in GitHub Desktop.
Save stuft2/ce40d16f315812533ceca5c988d69dbe to your computer and use it in GitHub Desktop.
Unit Testing with TypeScript

Unit Testing with TypeScript

Setting up Unit Tests with TypeScript is sometimes less than intuitive. My aim here is to present the pros and cons of using Typescript and to detail how simple it is to set up typescript unit tests with Jest and Mocha.


So, Why TypeScript?

  1. Avoid hidden errors such as 'undefined' is not a function
  2. Easier to refactor code
  3. Self-documenting code
  4. Transpiles into the various releases of Javascript and it's common runtime environments.

Why Not TypeScript?

In my short time as a developer I've identified a few pitfalls of TypeScript.

  1. JS makes no assumptions about types which forces developers to write type checks.
  2. Adds complexity to the code which may make it less readable.

Why TypeScript in Unit Tests?

You might be asking right now, "Why would I use Typescript for unit testing?"

To that I ask, what is more detrimental to customers:

  • A product that takes longer to release because testing caught errors?
  • Or a product that doesn't work because the tests didn't catch the errors?

If using Typescript helps developers to make fewer errors in their source code, wouldn't the same logic apply to our unit tests?

Regardless if your toolset includes TypeScript, being consistent and consolidating around the same language or process will speed up the development process.


Setup Typescript

  1. Install dependencies

npm i -D typescript @types/node @tsconfig/node14 del-cli

  1. Add tsconfig that extends @tsconfig/node14/tsconfig.json
{
  "extends": "@tsconfig/node14/tsconfig.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": [
    "src/**/*"
  ]
}
  1. Add clean and build scripts to package.json file
{
  "scripts": {
    "build": "npm run clean && tsc",
    "clean": "del-cli dist"
  }
}
  1. In the terminal, run npm run build to make sure the build step works. You should see a dist directory appear at your project root.

Typescript + Jest

Installation and setup is straight-forward and simple for the majority of use-cases.

  1. Install dependencies

npm i -D jest ts-jest @types/jest

  1. Add jest configuration to the package.json file
{
  "jest": {
    "transform": {
      "^.+\\.(ts|tsx)$": "ts-jest"
    },
    "coverageThreshold": {
      "branches": 80,
      "lines": 80,
      "functions": 80,
      "statements": 80
    }
  }
}
  1. Add test and coverage scripts to the package.json file
{
  "scripts:": {
    "coverage": "jest --coverage || exit 0",
    "test": "jest"
  }
}
  1. Add tests to the test directory (see TS-Jest Example)

  2. In your terminal, run npm test or npm coverage. You should see a coverage directory appear at your project root, if your tests completed.

TS-Jest Example

import {add} from '../src'

describe('add', () => {
    it('should add a single number', () => {
        expect(add(1)).toEqual(1)
    })
    it('should add multiple numbers together', () => {
        expect(add(1,2,3,4,5)).toEqual(15)
    })
    it('should add negative numbers together', () => {
        expect(add(-1,-2,-3,-4,-5)).toEqual(-15)
    })
    it('should throw a type error if an argument isn\'t a number', () => {
        expect(() => add(null)).toThrow(TypeError)
    })
})

Typescript + Mocha/Chai

Mocha test dependencies are much more spread out so there's more to install. That's not to say that it's significantly more complicated to set up, but it may be slightly more cumbersome to keep dependencies up-to-date.

  1. Install dependencies

npm i -D mocha ts-mocha @types/mocha chai @types/chai nyc

Additionally, sinon may be installed to add spies and mock functions.

  1. Add test and coverage scripts to the package.json file
{
  "scripts": {
    "coverage": "nyc npm test || exit 0",
    "test": "ts-mocha test/**/*.ts"
  }
}
  1. Add the follow nyc configuration file nyc.config.js to the root of your project
'use strict'

const {join} = require('path')
const {tmpdir} = require('os')
const {realpathSync} = require('fs')

const getCacheDirectory = () => {
  const {getuid} = process
  const tmpdirPath = join(realpathSync(tmpdir()), 'jest')
  if (getuid == null) {
    return tmpdirPath
  }
  // On some platforms tmpdir() is `/tmp`, causing conflicts between different
  // users and permission issues. Adding an additional subdivision by UID can
  // help.
  return `${tmpdirPath}_${getuid.call(process).toString(36)}`
}

module.exports = {
  'reporter': ['json', 'lcov', 'text', 'clover'],
  'temp-dir': getCacheDirectory(),
  'check-coverage': true,
  'branches': 80,
  'lines': 80,
  'functions': 80,
  'statements': 80
}
  1. Add tests to the test directory (see TS-Mocha Example)

  2. In your terminal, run npm test or npm coverage. You should see a coverage directory appear at your project root, if your tests completed.

TS-Mocha Example

import {expect} from 'chai'
import {add} from '../src'

describe('add', () => {
    it('should add a single number', () => {
        expect(add(1)).to.equal(1)
    })
    it('should add multiple numbers together', () => {
        expect(add(1,2,3,4,5)).to.equal(15)
    })
    it('should add negative numbers together', () => {
        expect(add(-1,-2,-3,-4,-5)).to.equal(-15)
    })
    it('should throw a type error if an argument isn\'t a number', () => {
        expect(() => add(null)).to.throw(TypeError)
    })
})

One last thing...

When stubbing functions, classes, or objects in TypeScript, you'll have to make use of type aliasing and overwrites to not end up stubbing everything

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment