Testing with Jest and TypeScript, the tricky parts
Tags: testing, typescript
Published: 2020-02-17 17:09:31

Recently, I started with a side project that uses TypeScript in the frontend and in the backend. I wanted to do things test-driven and chose the [Jest](https://jestjs.io) framework as it is a very popular choice. 

When using Jest with TypeScript, I encountered some struggles and pitfalls I ran into.

I would like to share the learnings I had 👩‍💻🙇‍♀️🤷‍♀️🤦‍♀️👩‍🎤😊.

# Using Jest with TypeScript

In the first place, jest recommends to use TypeScript via Babel in their [documentation](https://jestjs.io/docs/en/getting-started#using-typescript). 

I couldn't get Babel configured correctly, so it did not work for me. I used the alternative approach via [ts-jest](https://kulshekhar.github.io/ts-jest/):


```sh
npm install --save-dev jest typescript ts-jest @types/jest
npx ts-jest config:init
```

It generates a `jest.config.js` file with: 
```js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};
```

If you are testing browser stuff, you can change testEnvironment to `'jsdom'` to get  access to DOM APIs in your tests.

# Mocking stuff in TypeScript

When I first tried to use mocks in TypeScript, I got a lot of type errors when trying to access properties from the mock (eg. `mockClear()`). 

I figured out `ts-jest` provides a `mocked()` wrapper function that adds all mock properties to the function or object you would like to mock.

## Example:

Let's look at an example app that fetches a person from the [Star Wars API](http://swapi.co)

```ts
// api.ts

import fetch from 'node-fetch';

const BASE_URL = 'http://swapi.co/api/'

export async function getPerson(id: number) {
  const response = await fetch(BASE_URL + `people/${id}/`);
  const data = await response.json();
  return data;
}

// index.ts
  
import { getPerson } from "./api";

async function main() {
  const luke = await getPerson(1);
  console.log(luke);
}

main();
```

The testing code mocks `fetch` and provides a mock implementation for it:

```ts
// api.test.ts

import fetch from 'node-fetch';
import { mocked } from 'ts-jest/utils';
import { getPeople } from './api';

jest.mock('node-fetch', () => {
  return jest.fn();
});

beforeEach(() => {
  mocked(fetch).mockClear();
});

describe('getPeople test', () => {
  test('getPeople should fetch a person', async () => {

    // provide a mock implementation for the mocked fetch:
    mocked(fetch).mockImplementation((): Promise<any> => {
      return Promise.resolve({
        json() {
          return Promise.resolve({name: 'Luke Vader'});
        }
      });
    });

    // getPeople uses the mock implementation for fetch:
    const person = await getPeople(1);
    expect(mocked(fetch).mock.calls.length).toBe(1);
    expect(person).toBeDefined();
    expect(person.name).toBe('Luke Vader');
  });
});
```

# Ignore css/scss/less imports

By default, jest tries to parse css imports as JavaScript. In order to ignore all things css, some extra configuration is needed.

Add a stub-transformer to your project which returns css imports as empty objects:

```js
module.exports = {
  process() {
    return 'module.exports = {};';
  },
  getCacheKey() {
    return 'stub-transformer';
  },
};
```

Add this to your `jest.config.js` configuration file:

```js
module.exports = {

  // ...
  transform: {
    "\\.(css|less|scss)$": "./jest/stub-transformer.js"
  }
};
```

# node Express: mocking an express Response object

I was struggling with testing express routes on the backend side. I figured out that I don't need a complete implementation of the express response object. It's sufficient to implement just the properties you actually use. So, I came up with a minimal fake `Partial<Response>` object, wrapped into a `Fakexpress` class:

```ts
import { Response } from 'express';

export class Fakexpress {

  constructor(req: any) {
    this.req = req;
  }

  res : Partial<Response> = {
    statusCode: 200,
    status: jest.fn().mockImplementation((code) => {
      this.res.statusCode = code;
      return this.res;
    }),
    json: jest.fn().mockImplementation((param) => {
      this.responseData = param;
      return this.res;
    }),
    cookie: jest.fn(),
    clearCookie: jest.fn()
  }

  req: any;
  responseData: any;
}
```

The test code looks like this:

```ts
  test('Testing an express route', async () => {
    const xp = new Fakexpress({
      params: {
        name: 'max'
      }
    });

    const searchResult = {
      name: 'max',
      fullName: 'Max Muster',
      description: 'Full Stack TypeScript developer',
      pronouns: 'they/them',
    };
    await expressRoute(xp.req as Request, xp.res as Response);
    expect(xp.responseData).toStrictEqual(searchResult);
    expect(xp.res.statusCode).toBe(200);
  });
```