Manuel Schoebel
Thursday, 22. November 2018

Jest unit and snapshot testing with TypeScript in a next.js app

Since I was asked, I will also throw Jest for testing into the stack composed of next.js and TypeScript. So let’s have a look into how that can be achieved.

Add Jest

First we need to add Jest:

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

Next we need to update our package.json for jest configs and to also add the run script:

// package.json
{
  ...
  "scripts": {
    ...
    "test": "jest",
  }
  ...
  "jest": {
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js"
    ],
    "transform": {
      "^.+\\.tsx?$": "ts-jest"
    },
    "testMatch": [
      "**/*.(test|spec).(ts|tsx)"
    ],
    "globals": {
      "ts-jest": {
        "babelConfig": true,
        "tsConfig": "jest.tsconfig.json"
      }
    },
    "coveragePathIgnorePatterns": [
      "/node_modules/"
    ],
    "coverageReporters": [
      "json",
      "lcov",
      "text",
      "text-summary"
    ],
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/mocks.js",
      "\\.(css|less)$": "<rootDir>/__mocks__/mocks.js"
    }
  }
}

As you can see in the jest config, we specified an additional tsconfig file. So we create this one next:

// jest.tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "esnext",
    "jsx": "react",
    "sourceMap": false,
    "experimentalDecorators": true,
    "noImplicitUseStrict": true,
    "removeComments": true,
    "moduleResolution": "node",
    "lib": ["es2017", "dom"],
    "typeRoots": ["node_modules/@types", "src/@types"]
  },
  "exclude": ["node_modules", "out", ".next"]
}

Running the test script with npm run test lets us know, that jest cannot find any test. Makes sense, since we have not yet create one. So let us add a very simple unit test. First we create a function that we want to test:

// components/Button.tsx
import * as React from 'react'

export function giveMeFive(): number {
  return 5
}

type Props = {
  buttonText: string
}

export default (props: Props) => (
  <button onClick={e => console.log(giveMeFive())}>{props.buttonText}</button>
)

We added a small function, that we also export so we can test it. Next we will add the jest test:

// components/__test__/button.tests.ts
import { giveMeFive } from '../Button'

test('generateAttributeIds', () => {
  expect(giveMeFive()).toBe(5)
})

When running the test it succeeds and jest runs with TypeScript.

Testrun with jest and TypeScript

Snapshot testing with jest and TypeScript

At first more packages… Yay!

npm i -D react-test-renderer

Next we can add a snapshot test (note that we create a tsx file this time):

// components/__tests__/button.snapshot.test.tsx
import * as React from 'react'
import Button from '../Button'
import renderer from 'react-test-renderer'

it('renders correctly', () => {
  const tree = renderer.create(<Button buttonText="Some Text" />).toJSON()
  expect(tree).toMatchSnapshot()
})

Running the test shows again everything is successful. Since it was the first time we were running the snapshot test, a snapshot is created. You can find them within the newly created __snapshots__ folder.

Snapshot folder

Now let us change the Button component just a little bit. And pretend it was an accident:

import * as React from 'react'

export function giveMeFive(): number {
  return 5
}

type Props = {
  buttonText: string
}

export default (props: Props) => (
  <button onClick={e => console.log(giveMeFive())}>{props.buttonText}!</button>
)

Do you see the difference? No? It is hard to find… But let us run the test once again:

Snapshot test failed

Now we can see there is a ! that was added. Since we really like it, we can now update the snapshot:

npm test -- -u

And run the test again with npm run test. We can see the snapshot was updated and the new testrun is successful again:

Snapshot test udpated

That is it for now, I hope it was useful :)

Teile den Artikel, wenn er dir gefallen hat:

Du willst wissen wenn es etwas Neues gibt?
Dann melde dich kurz bei meinem Newsletter an!

Durch Anmeldung bei meinem Newsletter willigst du zur Erfolgsmessung, dem Einsatz des Versanddienstleisters MailChimp und Protokollierung der Anmeldung zu. Mehr Informationen dazu und zu deinen Widerrufsrechten findest du hier in der Datenschutzerklärung.