DEV Community

Lea Rosema (she/her) for Studio M - Song

Posted on • Updated on

Testing with Jest and TypeScript, the tricky parts

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 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.

I couldn't get Babel configured correctly, so it did not work for me. I used the alternative approach via ts-jest:

npm install --save-dev jest typescript ts-jest @types/jest
npx ts-jest config:init
Enter fullscreen mode Exit fullscreen mode

It generates a jest.config.js file with:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};
Enter fullscreen mode Exit fullscreen mode

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

// 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();
Enter fullscreen mode Exit fullscreen mode

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

// 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');
  });
});
Enter fullscreen mode Exit fullscreen mode

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:

module.exports = {
  process() {
    return 'module.exports = {};';
  },
  getCacheKey() {
    return 'stub-transformer';
  },
};
Enter fullscreen mode Exit fullscreen mode

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

module.exports = {

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

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:

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;
}
Enter fullscreen mode Exit fullscreen mode

The test code looks like this:

  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);
  });
Enter fullscreen mode Exit fullscreen mode

Top comments (9)

Collapse
 
wolfhoundjesse profile image
Jesse M. Holmes

I ran into something in this category with mocking a module with jest.mock().

import { fetchResource } from 'utils/api'

jest.mock('utils/api')
Enter fullscreen mode Exit fullscreen mode

This alone is enough to mock fetchResource, but with TypeScript we still get the intellisense from the original function rather than the mock function. I just reassigned fetchResource and typed the new function:

import { fetchResource } from 'utils/api'

jest.mock('utils/api')
const mockFetch = fetchResource as jest.Mock
Enter fullscreen mode Exit fullscreen mode

Finally I had access to things like fetchResource.mockResolvedValueOnce()

Collapse
 
learosema profile image
Lea Rosema (she/her)

Cool, learned something new today, thank you =)

Collapse
 
pscoriae profile image
Pierre Cesario

You just ended my hour long search for a solution. Many thanks!

Collapse
 
ngryman profile image
Nicolas Gryman • Edited

TIL I learned:

import { mocked } from 'ts-jest/utils'
Enter fullscreen mode Exit fullscreen mode

Thanks for the tip! This is really helpful to avoid 👊ing against Typescript when manipulating mocks.

That's probably a matter of taste, but I think the mock implementation could be simplified to something like this:

import fetch, { Response } from 'node-fetch'

mocked(fetch).mockImplementation(
  async () =>
    <Response>{
      json: async () => {name: 'Luke Vader'}
    }
)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
starkiedev profile image
James Starkie

Thanks very much for this, I was struggling to mock node-fetch in TypeScript and this was very helpful :)

Collapse
 
learosema profile image
Lea Rosema (she/her)

Awesome, glad it was helpful to you :)

Collapse
 
hantsy profile image
Hantsy Bai

I tried to mock mongoose.connect in my Nestjs, but failed, stackoverflow.com/questions/629087...

Collapse
 
thejaswimanju profile image
thejaswimanju

Is there a alternative of ts-jest/utils mocked, in the latest versions of ts-jest I do not see any support of mocked, let me know if there is any solution for this.

Thanks

Collapse
 
chrisgilbert profile image
Chris Gilbert

Yes, the mocked test helper is actually in the main jest library now, as jest.mocked(). It seems to have been removed here: github.com/kulshekhar/ts-jest/pull...