Testing TypeScript with Jest

Even with TypeScript in the mix, ts-jest makes testing React applications easy. This is the fourth entry in a short series about creating React/Redux applications with TypeScript. Part one introduced a simple counter application, which we then dressed up with a simple React UI and API interactions. If you’re in a hurry, skip on over to the finished project on Github; everyone else, read on!

Followers of this blog won’t need to be convinced of the value of a well-maintained test suite. Lucky for us, Jest makes it nearly painless to test React applications–even when TypeScript is added to the mix. Let’s dig through the layers involved in developing clean, test-secured code.

Linting

In vanilla JavaScript, linters validate syntax as a matter of style and a first line of defense against runtime errors. In TypeScript, static type-checking serves a similar purpose, though with a more sophisticated understanding of the raw syntax’s context. But linters still add value for the maintainer: even with a type-system handling the heavy lifting of static analysis, we still benefit from legible, consistent style.

tslint is a first-rate TypeScript linter with similar behavior to JavaScript tools like jshint or eslint.

$ npm install -g tslint
$ tslint --init

After installing tslint, and initializing a default configuration, we’re ready to lint a new or existing project:

$ tslint -c tslint.json 'src/**/*.{ts,tsx}'

Unit testing with Jest

Jest is a low-configuration testing harness popular in React applications, and for good reason: in vanilla JavaScript, it mostly Just Works™.

Using Jest with TypeScript takes a little more effort. Facebook’s Jest/TypeScript example outlines the strategy: set up a preprocessor like ts-jest to handle compilation and source-mapping, then feed the processed files to jest.

$ npm install --save-dev ts-jest

To configure jest, let’s add a new "jest" configuration into the project’s package.json and use it to preprocess typescript files.

{
  "...": "...",
  "jest": {
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js"
    ],
    "transform": {
      "\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
    },
    "testRegex": "/__tests__/.*\\.(ts|tsx|js)$"
  }
}

While package.json is open, we can add scripts that will run both the linter and tests.

{
  "...": "...",

  "scripts": {
    "lint": "tslint -c tslint.json 'src/**/*.{ts,tsx}'",
    "pretest": "npm run lint",
    "test": "jest"
  }
}

Add some tests

At this point, we can write a short spec to secure the rendering of our <Counter /> component and a jest snapshot.

// src/components/__tests__/counter_spec.tsx
import * as React from 'react'
import * as TestUtils from 'react-dom/test-utils'
import * as ReactShallowRenderer from 'react-test-renderer/shallow'

import { createStore } from 'redux'

import { Counter } from '../counter'
import { reducers } from '../../reducers'

describe('<Counter />', () => {
  it('renders', () => {
    const store = createStore(reducers)
    const renderer = new ReactShallowRenderer()

    expect(renderer.render(
      <Counter label='a counter!' store={store} />
    )).toMatchSnapshot()
  })
});

Though we’ll use ReactShallowRenderer to get started, the same setup can be used to test components with Enzyme. Whatever renderer we use, we can then compare snapshots exactly as we would in JavaScript.

All that’s left is to watch the test turn green.

$ npm test

Testing reducers

Redux reducers are meant to be easy to test. Using the same tools as with the component, we can verify reducer behavior by replaying a sequence of actions and comparing the resulting state:

// src/reducers/__tests__/index_spec.tsx
import reducers, {
  initialState,
} from '../index'

import {
  incrementCounter
} from '../../actions'

const resultOf = actions =>
  actions.reduce(reducers, initialState)

describe('reducers/counter', () => {
  it('starts at 0', () =>
    expect(resultOf([])).toMatchSnapshot())

  it('increments to 6', () =>
    expect(resultOf([
      incrementCounter(1),
      incrementCounter(2),
      incrementCounter(3),
    ])).toMatchSnapshot())
})

Conclusion

There you have it: the basic tools, configurations, and scripts to make testing a central part of a TypeScript application. We don’t have to stop here! Though it’s an exercise for another day, the same techniques we use to validate code and write unit tests can just as easily apply in end-to-end testing.

The completed counter project is available for reference on github, tests and all. Have an comment, suggestion, or improvement? The issue queue is open for business. And I’m looking forward to your suggestions, experiences, and feedback over on twitter.

And as ever, happy testing!

Up next: We’ll use the redux-thunk middleware to tighten down the counter’s API interactions. Read on!

Hey, I'm RJ! For more learnings about software and management, find me @rjzaworski or sign up for my semi-regular newsletter.

Let’s keep in touch

Send me timely updates on software, product, and process.