Search code examples
typescriptjestjsts-jest

Runtime error in jest - TypeError: Class extends value undefined is not a constructor or null


I have a typescript file defined like so:

// myType.tsx

import { Foo } from 'foo';
[...]
export interface MyType extends IDisposable {
[...]
}

export abstract class MyTypeBase implements MyType {
  innerFoo: Foo | null | undefined;
  get foo(): Foo {
    return this.innerFoo;
  }

  [...]
}

I am trying to test another class which depends on this MyType, and I want to mock out the foo() getter.

In my test file, once I add the following to the file:

[...]
import { MyType } from 'path/to/MyType';
[...]
jest.mock('path/to/MyType', () => {
  return jest.fn().mockImplementation(() => {
    return {foo: () => mockFoo}; //mockFoo is defined earlier in the file.
  })
});

I get an error when running the test: Class extends value undefined is not a constructor or null

This only occurs when I include the jest.mock(...) line.

From other questions related, I see this can be caused by circular dependencies, but I'm failing to see how that can introduced by a call to jest.mock? I can see that other breakpoints that are hit when I comment out this code are not being hit, so it appears as though adding this mock is causing my test to fail early for this reason.

Anyone come across this before?


Solution

  • The problem is explained in the documentation:

    A limitation with the factory parameter is that, since calls to jest.mock() are hoisted to the top of the file, it's not possible to first define a variable and then use it in the factory. An exception is made for variables that start with the word 'mock'. It's up to you to guarantee that they will be initialized on time! For example, the following will throw an out-of-scope error due to the use of 'fake' instead of 'mock' in the variable declaration

    The use of mockFoo variable name allows to use it inside jest.mock factory function at your own risk.

    mockFoo is defined earlier in the file

    This is not true as jest.mock hoisted above respective import but a variable it refers is not defined at the time when mocked module is evaluated. For eagerly evaluated mocked modules a mock needs to be defined inside a factory. In case it needs to be accessed in a test, it can be exposed via a module.

    foo is property accessor but is mocked as a function.

    Also mocked module is CommonJS, this will prevent named imports from being correctly mapped in most setups, there should be __esModule: true in mocked module.