jest typescript property mock does not exist on type

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?


Solution 1:

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

Solution 2:

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
});

Solution 3:

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))
}