Testing TypeScript with Jest
- 12/11/2016
- ·
- #typescript
- #jest
- #testing
- #howto
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!
Longtime readers won’t need to be convinced of the value of a well-maintained test suite. 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)$": "ts-jest"
},
"testRegex": "/__tests__/.*\\.(ts|tsx|js)$"
}
}
While package.json
is open, we can add additional scripts for the linter and
unit 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!