98

When using jest.fn() to add a mock you can usually access the .mock property to access details such as calls, something similar to this:

test('not working', () => {
    const foo = new Foo();
    foo.addListener = jest.fn();
    foo.func(); // will call addListener with a callback
    const callback = foo.addListener.mock.calls[0][0];
    expect(callback()).toEqual(1); // test the callback
});

When implementing the test in typescript instead of plain javascript I get the error:

error TS2339: Property 'mock' does not exist on type '(callback: () => number) => void'.

I can get rid of the error by casting to any but surely there must be a better way:

const callback = (foo.addListener as any).mock.calls[0][0];

In this simple code the mock could be rewritten to store the argument using jest.fn(fn => { callback = fn; }); but the same error happens when using foo.addListener.mockClear() which cannot be reworked the same way.

So how can I get rid of the error, preferably without losing type-safety?

4 Answers 4

116

For anyone getting here, a bit better than casting to any might be casting as jest.Mock

const callback = (foo.addListener as jest.Mock).mock.calls[0][0];

Update Sep 2021

To get a mocked function that both fulfills the mocked function type and the jest mock type jest.MockedFunction can be used:

const addListenerMock = addListener as jest.MockedFunction<typeof addListener>;
Sign up to request clarification or add additional context in comments.

Comments

63

You can use jest.spyOn in combination with functions like mockImplementation to mock a function while preserving type safety in TypeScript:

class Foo {
  addListener = (callback: () => number) => { }
  func = () => {
    this.addListener(() => 1);
  }
}

test('working', () => {
  const foo = new Foo();
  const mockAddListener = jest.spyOn(foo, 'addListener'); // spy on foo.addListener
  mockAddListener.mockImplementation(() => { }); // replace the implementation if desired
  foo.func(); // will call addListener with a callback
  const callback = mockAddListener.mock.calls[0][0];
  expect(callback()).toEqual(1); // SUCCESS
});

2 Comments

Is there a way to mock a function in the class without initiating it in the test?
The only catch to this approach is you need to import the whole library as in import * as _ from '', because the spy is applied to an object.
17

Got the error below when using axios.

TS2339 (TS) Property 'mockResolvedValueOnce' does not exist on type 'AxiosStatic'

Tried using axios as jest.Mock but got the error below:

TS2352 (TS) Conversion of type 'AxiosStatic' to type 'Mock<any, any>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'AxiosStatic' is missing the following properties from type 'Mock<any, any>': getMockName, mock, mockClear, mockReset, and 12 more.

Solved it by specifying as axios as unknown as jest.Mock

AxiosRequest.test.tsx

import axios from 'axios';
import { MediaByIdentifier } from '../api/mediaController';

jest.mock('axios', () => jest.fn());

test('Test AxiosRequest',async () => {
    const mRes = { status: 200, data: 'fake data' };
    (axios as unknown as jest.Mock).mockResolvedValueOnce(mRes);
    const mock = await MediaByIdentifier('Test');
    expect(mock).toEqual(mRes);
    expect(axios).toHaveBeenCalledTimes(1);
});

mediaController.ts:

import { sendRequest } from './request'
import { AxiosPromise } from 'axios'
import { MediaDto } from './../model/typegen/mediaDto';

const path = '/api/media/'

export const MediaByIdentifier = (identifier: string): AxiosPromise<MediaDto> => {
    return sendRequest(path + 'MediaByIdentifier?identifier=' + identifier, 'get');
}

request.ts:

import axios, { AxiosPromise, AxiosRequestConfig, Method } from 'axios';

const getConfig = (url: string, method: Method, params?: any, data?: any) => {
     const config: AxiosRequestConfig = {
         url: url,
         method: method,
         responseType: 'json',
         params: params,
         data: data,
         headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json' },
    }
    return config;
}

export const sendRequest = (url: string, method: Method, params?: any, data?: any): AxiosPromise<any> => {
    return axios(getConfig(url, method, params, data))
}

1 Comment

someone might do axios.get()
8

jest.spyOn is the right way to go.

However, if you'd like to do the same with a simple function that's not a part of a class, you can use jest.mocked. Here's an example importing functions from Firebase:

/** Example code depending on a simple function */
import {
  addDoc,  // Function to be mocked
  collection,
  getFirestore
} from 'firebase/firestore';

export async function createRecord(record: any) {
  const collectionRef = collection(getFirestore(), 'example-path');

  return addDoc(collectionRef, record);
}
/** Unit tests for example code */
import { addDoc } from 'firebase/firestore';

jest.mock('firebase/firestore');

describe('Create record', () => {
  test('creates a new record', async () => {
    const mockedAddDoc = jest.mocked(addDoc);

    await createRecord({ hello: 'world' });

    expect(mockedAddDoc.mock.calls[0][0]).toMatchObject({ hello: 'world' });
  });
});

https://jestjs.io/docs/jest-object#jestmockedtitem-t-deep--false

1 Comment

the jest.mocked(dependency) tip here is the key 🔑

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.