jest.mock():如何使用工厂参数

时间:2017-11-20 22:14:33

标签: javascript ecmascript-6 jestjs

模拟ES6类导入

我想在我的测试文件中模拟我的ES6类导入。

如果被模拟的类有多个使用者,将模拟移动到__mocks__可能是有意义的,这样所有测试都可以共享模拟,但在此之前我想将模拟保留在测试文件中

Jest.mock()

jest.mock()可以模拟导入的模块。传递一个参数时:

jest.mock('./my-class.js');

它使用与模拟文件相邻的__mocks__文件夹中的模拟实现,或创建自动模拟。

模块工厂参数

jest.mock()采用第二个参数,这是一个模块工厂功能。 对于使用export default导出的ES6类,它不清楚此工厂函数应返回的内容。 是吗:

  1. 另一个返回模仿类实例的对象的函数?
  2. 模仿类实例的对象?
  3. 具有属性default的对象是一个返回模仿该类实例的对象的函数吗?
  4. 返回高阶函数的函数,该函数本身返回1,2或3?
  5. The docs非常模糊:

      

    第二个参数可用于指定正在运行的显式模块工厂,而不是使用Jest的自动锁定功能:

    我努力想出一个工厂定义,当消费者import成为类时,它可以作为构造函数。我一直得到TypeError: _soundPlayer2.default is not a constructor(例如)。

    我试过避免使用箭头函数(因为它们不能用new调用)并让工厂返回一个具有default属性的对象(或不是)。

    这是一个例子。这不起作用;所有测试都抛出TypeError: _soundPlayer2.default is not a constructor

    正在测试的类: 的声玩家consumer.js

    import SoundPlayer from './sound-player'; // Default import
    
    export default class SoundPlayerConsumer {
      constructor() {
        this.soundPlayer = new SoundPlayer(); //TypeError: _soundPlayer2.default is not a constructor
      }
    
      playSomethingCool() {
        const coolSoundFileName = 'song.mp3';
        this.soundPlayer.playSoundFile(coolSoundFileName);
      }
    }
    

    被嘲笑的类: 的声音player.js

    export default class SoundPlayer {
      constructor() {
        // Stub
        this.whatever = 'whatever';
      }
    
      playSoundFile(fileName) {
        // Stub
        console.log('Playing sound file ' + fileName);
      }
    }
    

    测试文件: sound-player-consumer.test.js

    import SoundPlayerConsumer from './sound-player-consumer';
    import SoundPlayer from './sound-player';
    
    // What can I pass as the second arg here that will 
    // allow all of the tests below to pass?
    jest.mock('./sound-player', function() { 
      return {
        default: function() {
          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 mocked 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).toHaveBeenCalledWith(coolSoundFileName);
    });
    

    我可以将第二个arg传递给jest.mock(),它将允许示例中的所有测试通过?如果测试需要修改,那就好了 - 只要他们仍然测试相同的东西。

3 个答案:

答案 0 :(得分:1)

对于阅读此问题的任何人,我都设置了GitHub repository来测试模拟模块和类。它基于上面答案中描述的原理,但涵盖了默认导出和命名导出。

答案 1 :(得分:0)

如果您仍在使用TypeError: ...default is not a constructor并且正在使用TypeScript,请继续阅读。

TypeScript正在编译您的ts文件,您的模块很可能是使用ES2015s导入导入的。 const soundPlayer = require('./sound-player')。 因此,创建作为默认值导出的类的实例将如下所示:  new soundPlayer.default()。 但是,如果您按照文档中的建议嘲笑该类。

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

由于soundPlayer.default没有指向函数,您将得到相同的错误。 模拟必须返回一个对象,该对象的默认属性指向一个函数。

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

答案 2 :(得分:0)

如果你定义了一个模拟类,你可以使用类似的东西:

jest.mock("../RealClass", () => {
  const mockedModule = jest.requireActual(
    "../path-to-mocked-class/MockedRealClass"
  );
  return {
    ...mockedModule,
  };
});

代码会做一些事情,比如用 MockedRealClass 之一替换原始 RealClass 的方法和属性定义。