209

When testing a module that has a dependency in a different file and assigning that module to be a jest.mock, TypeScript gives an error that the method mockReturnThisOnce (or any other jest.mock method) does not exist on the dependency, this is because it is previously typed.

What is the proper way to get TypeScript to inherit the types from jest.mock?

Here is a quick example.

Dependency

const myDep = (name: string) => name;
export default myDep;

test.ts

import * as dep from '../depenendency';
jest.mock('../dependency');

it('should do what I need', () => {
  //this throws ts error
  // Property mockReturnValueOnce does not exist on type (name: string)....
  dep.default.mockReturnValueOnce('return')
}

I feel like this is a very common use case and not sure how to properly type this.

Lin Du
  • 88,126
  • 95
  • 281
  • 483
Philip Chmalts
  • 2,268
  • 2
  • 12
  • 11
  • 3
    If i remember right you have to mock before you import. Just switch the first 2 lines. But i'm not sure about this. – Thomas May 05 '18 at 14:39
  • 3
    @ThomasKleßen Modules imported via ES6 `import` are evaluated first, no matter if you put some code before the import. So this won't work. – mgol Sep 07 '18 at 13:58
  • @Thomas Calls to jest.mock are hoisted to the top of the code - jest magic I guess... ([ref](https://jestjs.io/docs/en/es6-class-mocks#replacing-the-mock-using-mockimplementationdocsenmock-function-apimockfnmockimplementationfn-or-mockimplementationoncedocsenmock-function-apimockfnmockimplementationoncefn)) However, this creates some pitfalls, e.g. when [calling jest.mock() with the module factory parameter](https://jestjs.io/docs/en/es6-class-mocks#calling-jestmockdocsenjest-objectjestmockmodulename-factory-options-with-the-module-factory-parameter) therefore name mock functions as `mock...` – Tobi Apr 22 '20 at 06:13

15 Answers15

183

You can use type casting and your test.ts should look like this:

import * as dep from '../dependency';
jest.mock('../dependency');

const mockedDependency = <jest.Mock<typeof dep.default>>dep.default;

it('should do what I need', () => {
  //this throws ts error
  // Property mockReturnValueOnce does not exist on type (name: string)....
  mockedDependency.mockReturnValueOnce('return');
});

TS transpiler is not aware that jest.mock('../dependency'); changes type of dep thus you have to use type casting. As imported dep is not a type definition you have to get its type with typeof dep.default.

Here are some other useful patterns I've found during my work with Jest and TS

When imported element is a class then you don't have to use typeof for example:

import { SomeClass } from './SomeClass';

jest.mock('./SomeClass');

const mockedClass = <jest.Mock<SomeClass>>SomeClass;

This solution is also useful when you have to mock some node native modules:

import { existsSync } from 'fs';

jest.mock('fs');

const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;

In case you don't want to use jest automatic mock and prefer create manual one

import TestedClass from './TestedClass';
import TestedClassDependency from './TestedClassDependency';

const testedClassDependencyMock = jest.fn<TestedClassDependency>(() => ({
  // implementation
}));

it('Should throw an error when calling playSomethingCool', () => {
  const testedClass = new TestedClass(testedClassDependencyMock());
});

testedClassDependencyMock() creates mocked object instance TestedClassDependency can be either class or type or interface

Artur Górski
  • 2,365
  • 2
  • 15
  • 14
  • 3
    I had to use `jest.fn(() =>...` instead of `jest.fn(() =>...` (I just removed the type casting after jest.fn) because IntelliJ is complaining. Otherwise this answer helped me thanks! Using this in my package.json: "@types/jest": "^24.0.3" – A. Masson Mar 14 '19 at 16:46
  • what does `jest.mock('./SomeClass');` in above code? – Reza Jul 31 '19 at 20:39
  • 19
    Hum it doesn't work anymore with last TS version and jest 24 :( – Vincent Oct 14 '19 at 08:57
  • 1
    @Reza it's auto mock, https://jestjs.io/docs/en/es6-class-mocks#automatic-mock – Bruce Lee Nov 04 '19 at 02:42
  • 32
    The `>SomeClass` expression is producing a TS error for me: `Conversion of type 'typeof SomeClass' to type 'Mock' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'typeof SomeClass' is missing the following properties from type 'Mock': getMockName, mock, mockClear, mockReset, and 11 more.ts(2352)` – the21st Apr 16 '20 at 14:57
  • 3
    @the21st In this case you should use something like `(SomeClass as unknown) as >`. Please note that this code is using another form of type casting, which is more preferred now. – Artur Górski Apr 16 '20 at 19:26
  • I see, thanks. Though at that point I think it's easier (and less ugly) to just use `// @ts-ignore` and on the next line e.g. `const mockedInstance = new SomeClass()` – the21st Apr 17 '20 at 09:32
  • See the answer by me for the proper solution: https://stackoverflow.com/a/60244371/1133802 – mostruash Aug 10 '20 at 07:12
  • Try `const SoundPlayerMock = SoundPlayer as jest.MockedClass;` – Tony I. Sep 06 '21 at 01:10
169

Use the mocked helper per the documentation

// foo.spec.ts
import { foo } from './foo'
jest.mock('./foo')

// here the whole foo var is mocked deeply
const mockedFoo = jest.mocked(foo, true)

test('deep', () => {
  // there will be no TS error here, and you'll have completion in modern IDEs
  mockedFoo.a.b.c.hello('me')
  // same here
  expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1)
})

test('direct', () => {
  foo.name()
  // here only foo.name is mocked (or its methods if it's an object)
  expect(jest.mocked(foo.name).mock.calls).toHaveLength(1)
})
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
François Romain
  • 13,617
  • 17
  • 89
  • 123
  • 3
    Here are a few more examples of using `ts-jest` and classes: https://github.com/tbinna/ts-jest-mock-examples and this post: https://stackoverflow.com/questions/58639737/import-function-from-a-jest-manual-mock-with-typescript/61358020#61358020 – Tobi Apr 22 '20 at 06:07
  • @Tobi The test in the repo fails – Kreator Sep 02 '20 at 12:24
  • Thanks for the heads-up @Kreator. Do you see the [same issue as the one reported](https://github.com/tbinna/ts-jest-mock-examples/issues/2)? I could not reproduce any issue yet. – Tobi Sep 04 '20 at 08:58
  • @Kreator just merged a PR. Let me know if the issue persists – Tobi Sep 04 '20 at 09:44
  • Is there an equivalent package, for us that have to run eslint? – Jeremy Feb 24 '21 at 07:37
  • 4
    Note that ts-jest/utils is now being deprecated in favour of the "official" jest-mocks package. – Rui Marques Jan 12 '22 at 12:29
  • 1
    TypeError: jest.mocked is not a function – mac Oct 19 '22 at 17:50
40

There are two solutions tested for TypeScript version 3.x and 4.x, both are casting desired function

1) Use jest.MockedFunction

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;

2) Use jest.Mock

import * as dep from './dependency';

jest.mock('./dependency');

const mockMyFunction = dep.default as jest.Mock;

There is no difference between these two solutions. The second one is shorter and I would therefore suggest using that one.

Both casting solutions allows to call any jest mock function on mockMyFunction like mockReturnValue or mockResolvedValue https://jestjs.io/docs/en/mock-function-api.html

mockMyFunction.mockReturnValue('value');

mockMyFunction can be used normally for expect

expect(mockMyFunction).toHaveBeenCalledTimes(1);
Black
  • 9,541
  • 3
  • 54
  • 54
  • 1
    Thank you! I like this more than the accepted answer because a) it's easier to read IMO and b) it works in JSX without causing syntax errors – Dan Fletcher Dec 13 '20 at 19:27
  • 3
    I get "typeError: mockMyFunction.mockReturnValue is not a function" – Spock Mar 27 '21 at 19:45
  • 1
    @Spock I've solved using require instead of import – Dr.G Dec 23 '21 at 14:43
  • 2 almost worked for me but my module does not have a default export so I got the following: ```TS2339: Property  default  does not exist on type``` But I was able to use 1 to just export the functions I needed. Thanks! – mikemachado Aug 08 '23 at 17:06
20

I use the pattern from @types/jest/index.d.ts just above the type def for Mocked (line 515):

import { Api } from "../api";
jest.mock("../api");

const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");
adanilev
  • 3,008
  • 3
  • 15
  • 20
  • 3
    I'm pretty sure you could just do `const myApi = new Api() as jest.Mocked;` – snowfrogdev Apr 04 '19 at 19:17
  • 4
    @neoflash: Not in strict mode in TypeScript 3.4 - it will complain that Api type does not sufficiently overlap with `jest.Mock`. You'd have to go with `const myApi = new Api() as any as jest.Mock` and I'd say the one above looks a bit better than double assertion. – paolostyle Apr 26 '19 at 21:37
  • @tuptus: is strict mode fresh for 3.4? Do you have a link please regarding this? – elmpp May 07 '19 at 12:00
  • @elmpp: not sure what you mean. By "strict mode" I meant having `"strict": true` in tsconfig.json. This covers stuff like `noImplicitAny`, `strictNullChecks` etc., so you don't have to set it to true for them individually. – paolostyle May 07 '19 at 15:20
  • I don't get it. Why are you only stubbing the method of one instance, i.e. `myApi`? It won't generically stub all other instances initiated by class `Api` within the module being tested, right? – Ivan Wang Jul 13 '20 at 05:17
  • 1
    What if API constructor parameters? Typescript is complaining I have to pass them even though I don't need to really. – Jens Nov 12 '20 at 20:39
  • I used `const myApi: jest.Mocked = new Api() as jest.Mocked;` to avoid eslint complaint about `any` – Scott Anderson Nov 02 '21 at 22:37
15

Cast as jest.Mock

Simply casting the function to jest.Mock should do the trick:

(dep.default as jest.Mock).mockReturnValueOnce('return')

exmaxx
  • 3,252
  • 28
  • 27
10

The latest jest allows you to do this very easily with jest.mocked

import * as dep from '../dependency';

jest.mock('../dependency');

const mockedDependency = jest.mocked(dep);

it('should do what I need', () => {
  mockedDependency.mockReturnValueOnce('return');
});
klugjo
  • 19,422
  • 8
  • 57
  • 75
8

Use as jest.Mock and nothing else

The most concise way of mocking a module exported as default in ts-jest that I can think of really boils down to casting the module as jest.Mock.

Code:

import myDep from '../dependency' // No `* as` here

jest.mock('../dependency')

it('does what I need', () => {
  // Only diff with pure JavaScript is the presence of `as jest.Mock`
  (myDep as jest.Mock).mockReturnValueOnce('return')

  // Call function that calls the mocked module here

  // Notice there's no reference to `.default` below
  expect(myDep).toHaveBeenCalled()
})

Benefits:

  • does not require referring to the default property anywhere in the test code - you reference the actual exported function name instead,
  • you can use the same technique for mocking named exports,
  • no * as in the import statement,
  • no complex casting using the typeof keyword,
  • no extra dependencies like mocked.
Chris Kobrzak
  • 1,044
  • 14
  • 20
  • I've just noticed someone's downvoted this answer without leaving any comment with the explanation. If this solution is no longer working, it would benefit the community if we highlighted what the issue is. – Chris Kobrzak Jun 12 '23 at 09:31
7

Here's what I did with jest@24.8.0 and ts-jest@24.0.2:

source:

class OAuth {

  static isLogIn() {
    // return true/false;
  }

  static getOAuthService() {
    // ...
  }
}

test:

import { OAuth } from '../src/to/the/OAuth'

jest.mock('../src/utils/OAuth', () => ({
  OAuth: class {
    public static getOAuthService() {
      return {
        getAuthorizationUrl() {
          return '';
        }
      };
    }
  }
}));

describe('createMeeting', () => {
  test('should call conferenceLoginBuild when not login', () => {
    OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
      return false;
    });

    // Other tests
  });
});

This is how to mock a non-default class and it's static methods:

jest.mock('../src/to/the/OAuth', () => ({
  OAuth: class {
    public static getOAuthService() {
      return {
        getAuthorizationUrl() {
          return '';
        }
      };
    }
  }
}));

Here should be some type conversion from the type of your class to jest.MockedClass or something like that. But it always ends up with errors. So I just used it directly, and it worked.

test('Some test', () => {
  OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
    return false;
  });
});

But, if it's a function, you can mock it and do the type conversation.

jest.mock('../src/to/the/Conference', () => ({
  conferenceSuccessDataBuild: jest.fn(),
  conferenceLoginBuild: jest.fn()
}));
const mockedConferenceLoginBuild = conferenceLoginBuild as 
jest.MockedFunction<
  typeof conferenceLoginBuild
>;
const mockedConferenceSuccessDataBuild = conferenceSuccessDataBuild as 
jest.MockedFunction<
  typeof conferenceSuccessDataBuild
>;
Bruce Lee
  • 4,177
  • 3
  • 28
  • 26
6

As of Jest 24.9.0 here is how you can mock and correctly type both your Class/Object/function and Jest properties.

jest.MockedFunction

jest.MockedClass

What we would like for a typed mock is that the mocked object type contains the union of the mocked object type and the type of Jest mocks.

import foo from 'foo';
jest.mock('foo');

const mockedFoo = foo as jest.MockedFunction<typeof foo>;
// or: const mockedFooClass = foo as jest.MockedClass<typeof FooClass>;


mockedFoo.mockResolvedValue('mockResult');

// Or:
(mockedFoo.getSomething as jest.MockedFunction<typeof mockedFoo.getSomething>).mockResolvedValue('mockResult');

As you can see, you can either manually cast what you need or you'll need something to traverse all foo's properties/methods to type/cast everything.

To do that (deep mock types) you can use jest.mocked() introduced in Jest 27.4.0

import foo from 'foo';
jest.mock('foo');

const mockedFoo = jest.mocked(foo, true); 

mockedFoo.mockImplementation() // correctly typed
mockedFoo.getSomething.mockImplementation() // also correctly typed
Rui Marques
  • 8,567
  • 3
  • 60
  • 91
5

I have found this in @types/jest:

/**
  * Wrap a function with mock definitions
  *
  * @example
  *
  *  import { myFunction } from "./library";
  *  jest.mock("./library");
  *
  *  const mockMyFunction = myFunction as jest.MockedFunction<typeof myFunction>;
  *  expect(mockMyFunction.mock.calls[0][0]).toBe(42);
*/

Note: When you do const mockMyFunction = myFunction and then something like mockFunction.mockReturnValue('foo'), you're a changing myFunction as well.

Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089

Milo
  • 59
  • 1
  • 1
2

The top rated solution by Artur Górski does not work with the last TS and Jest. Use MockedClass

import SoundPlayer from '../sound-player';

jest.mock('../sound-player'); // SoundPlayer is now a mock constructor

const SoundPlayerMock = SoundPlayer as jest.MockedClass<typeof SoundPlayer>;
Tony I.
  • 498
  • 4
  • 16
-1

A recent library solves this problem with a babel plugin: https://github.com/userlike/joke

Example:

import { mock, mockSome } from 'userlike/joke';

const dep = mock(import('./dependency'));

// You can partially mock a module too, completely typesafe!
// thisIsAMock has mock related methods
// thisIsReal does not have mock related methods
const { thisIsAMock, thisIsReal } = mockSome(import('./dependency2'), () => ({ 
  thisIsAMock: jest.fn() 
}));

it('should do what I need', () => {
  dep.mockReturnValueOnce('return');
}

Be aware that dep and mockReturnValueOnce are fully type safe. On top, tsserver is aware that depencency was imported and was assigned to dep so all automatic refactorings that tsserver supports will work too.

Note: I maintain the library.

mostruash
  • 4,169
  • 1
  • 23
  • 40
-1

This is ugly, and in fact getting away from this ugliness is why I even looked at this question, but to get strong typing from a module mock, you can do something like this:

const myDep = (require('./dependency') as import('./__mocks__/dependency')).default;

jest.mock('./dependency');

Make sure you require './dependency' rather than the mock directly, or you will get two different instantiations.

dx_over_dt
  • 13,240
  • 17
  • 54
  • 102
-1

For me this was enough:

let itemQ: queueItemType
jest.mock('../dependency/queue', () => {
    return {
        add: async (item: queueItemType, ..._args: any) => {
            // then we can use the item that would be pushed to the queue in our tests
            itemQ = item
            return new Promise(resolve => {
                resolve('Mocked')
            })
        },
    }
})

Then, whenever the add method is called it will execute this code above instead of pushing it to the queue, in this case.

Jonas Braga
  • 379
  • 4
  • 5
-1

With TypeScript 2.8 we can do like this with ReturnType:

import * as dep from "./depenendency"

jest.mock("./dependency")

const mockedDependency = <jest.Mock<ReturnType<typeof dep.default>>>dep.default

it("should do what I need", () => {
  mockedDependency.mockReturnValueOnce("return")
})
yelliver
  • 5,648
  • 5
  • 34
  • 65