Skip to content

TypeError: _MyClass2.default is not a constructor using jest.mock(path, factory) on ES6 class import #5023

@jonathan-stone

Description

@jonathan-stone

Do you want to request a feature or report a bug?

It's not clear from the docs whether this is expected behavior or not. So at a minimum it's a documentation bug and code feature request.

I'd be happy to submit a PR if this is confirmed to be a real issue.

What is the current behavior?
Assuming the following scenario: An ES6 class (MyClassConsumer) is being tested with Jest. That class imports another ES6 class (MyClass) and calls new MyClass() to create a new instance/object of that class. In the test for MyClassConsumer, MyClass is mocked since that class is not to be tested. See the demo repo for a full example, or see sample code at the bottom of this issue.

When mocking es6 classes using jest.mock('./my-class', ()=>{return {myFunc: jest.fn()}}), the mock does not function correctly. This results in the error TypeError: _MyClass2.default is not a constructor in file MyClassConsumer on the line where it calls new MyClass().

There is nothing that can be passed as the module factory parameter (2nd parameter to jest.mock()) that will correct this error.

There is a workaround, which is to use jest.mock() and then separately call MyClass.mockImplementation(...).

If the current behavior is a bug, please provide the steps to reproduce and
either a repl.it demo through https://repl.it/languages/jest or a minimal
repository on GitHub that we can yarn install and yarn test.

Repo demonstrating the issue is here:
https://github.com/jonathan-stone/jest-es6-classes-demo

Used create-react-app to generate a base React app, and added demo files into src/es6-classes-demo.

What is the expected behavior?

Documentation expected behavior:

The docs specifically mention how to mock ES6 class imports, with at least one example.

Code expected behavior:

Passing a module factory function into jest.mock() allows files that import the mocked class to call new on it without throwing an error.

Please provide your exact Jest configuration and mention your Jest, node,
yarn/npm version and operating system.

OS: MacOS Sierra 10.12.6
Jest version: 21.2.1
Node version: 8.9.0
NPM version: 5.5.1
Jest configuration: Various. Here's an example which is confirmed to repro the issue:

module.exports = {
  verbose: true,
  preset: 'react-native',
  setupFiles: ['./config/jest/setup-tests.js'],
  modulePathIgnorePatterns: ['<rootDir>/src/mocking-factory-tests']
};

In demo repo, Jest config is provided by react-scripts.
Create-react-app version: 1.4.3

(Demo repo uses jest version 20.0.4 since that's what CRA created. But same issue occurs with latest Jest.)

Example code:

Class being mocked - sound-player.js:

export default class SoundPlayer {
  constructor() {
    // Stub
    this.whatever = 'whatever';
  }

  playSoundFile(fileName) {
    // Stub
    console.log('Playing sound file ' + fileName);
  }
}

export {SoundPlayer};


Class being tested - sound-player-consumer.js

import SoundPlayer from './sound-player';

export default class SoundPlayerConsumer {
  constructor() {
    this.soundPlayer = new SoundPlayer();
  }

  playSomethingCool() {
    const coolSoundFileName = 'song.mp3';
    this.soundPlayer.playSoundFile(coolSoundFileName);
  }
}
 

Test that generates the error:

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

// These tests all fail when SoundPlayerConsumer calls the constructor of SoundPlayer,
// throwing TypeError: _soundPlayer2.default is not a constructor

jest.mock('./sound-player', () => {
  return {
    playSoundFile: jest.fn()
  };
});

it('The consumer should be able to call new() on SoundPlayer', () => {
  const soundPlayerConsumer = new SoundPlayerConsumer();
  expect(soundPlayerConsumer).toBeTruthy(); // Constructor ran with no errors
});

it('We can check if the consumer called the class constructor', () => {
  const soundPlayerConsumer = new SoundPlayerConsumer();
  expect(SoundPlayer).toHaveBeenCalled();
});

it('We can check if the consumer called a method on the class instance', () => {
  const soundPlayerConsumer = new SoundPlayerConsumer();
  const coolSoundFileName = 'song.mp3';
  soundPlayerConsumer.playSomethingCool();
  expect(SoundPlayer.playSoundFile.mock.calls[0][0]).toEqual(coolSoundFileName);
});

Test Output
FAIL src/es6-classes-demo/sound-player-consumer-factory-mock.test.js
● The consumer should be able to call new() on SoundPlayer

TypeError: _soundPlayer2.default is not a constructor
  
  at new SoundPlayerConsumer (src/es6-classes-demo/sound-player-consumer.js:5:24)
  at Object.<anonymous>.it (src/es6-classes-demo/sound-player-consumer-factory-mock.test.js:14:31)
      at new Promise (<anonymous>)
      at <anonymous>
  at process._tickCallback (internal/process/next_tick.js:188:7)

● We can check if the consumer called the class constructor

TypeError: _soundPlayer2.default is not a constructor
  
  at new SoundPlayerConsumer (src/es6-classes-demo/sound-player-consumer.js:5:24)
  at Object.<anonymous>.it (src/es6-classes-demo/sound-player-consumer-factory-mock.test.js:19:31)
      at new Promise (<anonymous>)
      at <anonymous>
  at process._tickCallback (internal/process/next_tick.js:188:7)

● We can check if the consumer called a method on the class instance

TypeError: _soundPlayer2.default is not a constructor
  
  at new SoundPlayerConsumer (src/es6-classes-demo/sound-player-consumer.js:5:24)
  at Object.<anonymous>.it (src/es6-classes-demo/sound-player-consumer-factory-mock.test.js:24:31)
      at new Promise (<anonymous>)
      at <anonymous>
  at process._tickCallback (internal/process/next_tick.js:188:7)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions