Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jest.mock doesn't modify the type of the mocked class #576

Closed
johngrogg opened this issue Jun 8, 2018 · 13 comments · May be fixed by procrafts/kata-diamond#20, procrafts/kata-diamond#21 or procrafts/kata-diamond#22

Comments

@johngrogg
Copy link

  • Issue

The documentation says:

Supports automatic of jest.mock() calls

However, when it is used, the auto-mocked class' type signature isn't updated with the jest mock extensions, which results in errors like:

error TS2339: Property 'mockClear' does not exist on type 'typeof TokenRepository'.

The only "simple" work around I've found is to typecast the mocked class as a jest.Mock like:

(TokenRepository as jest.Mock<TokenRepository>).mockClear();
  • Expected behavior

Ideally provide some way to update the type definitions of auto-mocked classes.

If that's not possible, then at least update the documentation to call out this limitation and provide samples of how to work around it.

@GeeWee
Copy link
Collaborator

GeeWee commented Jun 11, 2018

I'm not sure it's possible to automatically cast the class - if it is, it's beyond my typescript-fu.

Definitely agree the documentation could be clearer. Would love a PR

@huafu
Copy link
Collaborator

huafu commented Jul 24, 2018

Badly it's not possible to dynamically update a class like that. Modules could be overridden, but knowing their path and the overrides can only be written with static strings. So here it's the documentation that should be updated, nothing can be done code-wise.

example override of a module (I might be wrong at module file location, it's just an example):

import Vue from 'vue' // so that existing types aren't erased by our override
declare module 'vue/types' { // <== this issue would need this string to be dynamic
   interface VueConstructor { // already declared in 'vue/types.d.ts' package's file
     myProp: string // added prop
   }
}

A possible way of doing so (but I did not check and anyway it would be a big feature) is by creating a ts-server plugin.......

huafu added a commit to huafu/ts-jest that referenced this issue Sep 28, 2018
The `jest.mock()` does not change the type of the exported values from a
module. So we have to:
```ts
expect((foo as any).mock.calls[0][0]).toBe('foo')
```
Now with the `mocked` helper it is possible to do:
```ts
expect(mocked(foo).mock.calls[0][0]).toBe('foo')
```
It is also possible to deeply mock a module right after importing it.
Docs will be updated with related details.

Closes kulshekhar#576
@huafu
Copy link
Collaborator

huafu commented Sep 28, 2018

Can't do this by a language service plugin. Nothing even a lang service plugin can change the typing of an import, neither of a var. That is why I chose the helper way to fix this, see the PR

@Squiggle
Copy link

I fell down an internet hole for hours trying to figure this one out. Glad I found this thread!

For anyone else that ends up here, I refer you to mocked()

e.g.

import { mocked } from 'ts-jest/utils';
import { thingThatCallsMyFunc } from './ThingUnderTest'; // <-- this is the item under test
import { myFunc } from './MyDependency'; // <-- this is the dependecy we're mocking

jest.mock('./MyDependency'); // <-- gotta declare the module as mocked up here

describe('a test', () => {
   test('my test 1', () => {
      // and override the mocked behaviour down here
      mocked(myFunc).mockImplementation(() => true); // <-- because myFunc.mockImplementation(...) can't be done in typescript
      expect(thingThatCallsMyFunc.doSomething()).toBe(true);
   });

   test('my test 2', () => {
      mocked(myFunc).mockImplementation(() => false); // <-- different mock implementations per test
      expect(thingThatCallsMyFunc.doSomething()).toBe(false);
   });
})

@mrdulin
Copy link

mrdulin commented Mar 21, 2019

same issue. When call .mockClear method, got tsc error:

Property 'mockClear' does not exist on type 'typeof LocationDAOImpl'.

@tamj0rd2
Copy link

tamj0rd2 commented Aug 3, 2019

This doesn't work for classes

TypeScript diagnostics (customize using `[jest-config].globals.ts-jest.diagnostics` option):
    src/services/message-service.test.ts:9:3 - error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'typeof import("D:/coding/reddit-chaser/node_modules/ts-jest/dist/index")' has no compatible call signatures.

Example. Snoowrap is a class that I'm trying to mock. It's a class with a constructor, not an instance.

import Snoowrap = require("snoowrap")

jest.mock("snoowrap")

beforeEach(() => {
  var myMockClass = mocked(Snoowrap) // this generates the above error before I'm even able to mock it
})

This works, but then no typings are available:

import Snoowrap from "snoowrap"

const mockComposeMessage = jest.fn()
jest.mock("snoowrap", () => {
  return jest.fn().mockImplementation(() => ({
    composeMessage: mockComposeMessage
  }))
})
chai.should()

beforeEach(() => {
  mockComposeMessage.mockClear()
})

Referring to the docs here works great for javascript, but I can't figure this out at all for typescript

@sautille
Copy link

sautille commented Sep 3, 2019

mocked(myImportedModuleMethod).mockClear() works

@ArtemKha
Copy link

I fell down an internet hole for hours trying to figure this one out. Glad I found this thread!

For anyone else that ends up here, I refer you to mocked()

e.g.

import { mocked } from 'ts-jest/utils';
import { thingThatCallsMyFunc } from './ThingUnderTest'; // <-- this is the item under test
import { myFunc } from './MyDependency'; // <-- this is the dependecy we're mocking

jest.mock('./MyDependency'); // <-- gotta declare the module as mocked up here

describe('a test', () => {
   test('my test 1', () => {
      // and override the mocked behaviour down here
      mocked(myFunc).mockImplementation(() => true); // <-- because myFunc.mockImplementation(...) can't be done in typescript
      expect(thingThatCallsMyFunc.doSomething()).toBe(true);
   });

   test('my test 2', () => {
      mocked(myFunc).mockImplementation(() => false); // <-- different mock implementations per test
      expect(thingThatCallsMyFunc.doSomething()).toBe(false);
   });
})

this works perfect with react-boilerplate-typescript. thanks, man

@anilanar
Copy link

mocked is a pretty good solution. Another one is https://github.com/userlike/joke, if you are using babel to do some transforms already.

@davisokoth
Copy link

mocked is a pretty good solution. Another one is https://github.com/userlike/joke, if you are using babel to do some transforms already.

After 2 days of all sorts of gymnastics this one worked in a few minutes for me. Thanks for suggesting!

@ahgentil
Copy link

I fell down an internet hole for hours trying to figure this one out. Glad I found this thread!

For anyone else that ends up here, I refer you to mocked()

e.g.

import { mocked } from 'ts-jest/utils';
import { thingThatCallsMyFunc } from './ThingUnderTest'; // <-- this is the item under test
import { myFunc } from './MyDependency'; // <-- this is the dependecy we're mocking

jest.mock('./MyDependency'); // <-- gotta declare the module as mocked up here

describe('a test', () => {
   test('my test 1', () => {
      // and override the mocked behaviour down here
      mocked(myFunc).mockImplementation(() => true); // <-- because myFunc.mockImplementation(...) can't be done in typescript
      expect(thingThatCallsMyFunc.doSomething()).toBe(true);
   });

   test('my test 2', () => {
      mocked(myFunc).mockImplementation(() => false); // <-- different mock implementations per test
      expect(thingThatCallsMyFunc.doSomething()).toBe(false);
   });
})

For people still ending up here, this works for me without the need for mocked()

import { SomeClass } from './class-file';

jest.mock('./class-file');

describe('test', () => {
  const object = new SomeClass();

  test('first test', () => {
    object.get = jest.fn(() => null);

    // ...
  });

  test('second test', () => {
    object.get = jest.fn(() => 'blabla');
    object.save = jest.fn(() => { id: 1 });

    // ...
  });
});

I just can't find a way to do SomeClass.mockClear(); because tsc will complain that mockClear does not exist.

jonotp added a commit to jonotp/invoice that referenced this issue Oct 26, 2020
Needed to install ts-jest to avoid typescript errors when trying to clear mocked implementation. See link below:
kulshekhar/ts-jest#576

Also CRA has outdated jsdom (v14.1.0) which results in MutationObserver error when trying to use WaitFor's in react testing library. Needed to jsdom-sixteen package to run jest with jsdom v16. See the link below
testing-library/dom-testing-library#477
@tomsiwik
Copy link

tomsiwik commented Aug 2, 2021

@ahgentil try this:

import { mocked } from 'ts-jest/utils';
import { thingThatCallsMyFunc } from './ThingUnderTest';
import { myFunc } from './MyDependency';

jest.mock('./MyDependency');

describe('a test', () => {
   const mocks = {
     myFunc: mocked(myFunc)
   }
   
   afterEach(()=>{
     Object.values(mocks).forEach(mock => mock.mockClear())
   })
   
   test('my test 1', () => {
      // and override the mocked behaviour down here
      mocks.myFunc.mockImplementation(() => true);
      expect(thingThatCallsMyFunc.doSomething()).toBe(true);
   });

   test('my test 2', () => {
      mocks.myFunc.mockImplementation(() => false); // <-- You could have also used: mockImplementationOnce
      expect(thingThatCallsMyFunc.doSomething()).toBe(false);
   });
})

@smcelhinney
Copy link

The solution

;(Utilities as jest.Mock<Utilities>).mockClear()

worked for me but I had to prefix it with ; because I had a jest.resetAllMocks() on the previous line that wasn't terminating properly with the open parentheses on the next line, ie.

  beforeEach(() => {
    jest.resetAllMocks()
    ;(Utilities as jest.Mock<Utilities>).mockClear()
  })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment