Jest.js 笑话.模拟():如何使用工厂参数模拟ES6类默认导入

ttygqcqt  于 2023-03-06  发布在  Jest
关注(0)|答案(5)|浏览(211)

模拟ES6类导入

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

笑话模拟()

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

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

它使用在与mock文件相邻的__mocks__文件夹中找到的mock实现,或者创建自动mock。

模块工厂参数

jest.mock()接受第二个参数,这是一个模块工厂函数。***对于使用export default导出的ES6类,不清楚此工厂函数应返回什么。是否:
1.另一个返回模拟类示例的对象的函数?
1.模拟类示例的对象?
1.一个具有default属性的对象,该属性是一个返回模拟类示例的对象的函数?
1.一个函数返回一个本身返回1、2或3的高阶函数?
文件很含糊:
第二个参数可以用来指定一个正在运行的显式模块工厂,而不是使用Jest的自动锁功能:
当消费者import是类时,我很难找到一个工厂定义,它可以充当构造函数,我一直在得到TypeError: _soundPlayer2.default is not a constructor(例如)。
我尝试过避免使用箭头函数(因为它们不能用new调用),并让工厂返回一个具有default属性(或不具有)的对象。
这里有一个例子。这行不通;所有测试都抛出TypeError: _soundPlayer2.default is not a constructor
正在测试的类:
* 声音播放器-消费者. 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);
  }
}

被嘲笑的类:* * 声音播放器. js**

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

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

测试文件:* * 声音播放器-消费者.测试. 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);
});

我可以把什么作为第二个参数传递给jest.mock(),以允许示例中的所有测试都通过?如果测试需要修改,也没关系-只要它们仍然测试相同的东西。

deikduxw

deikduxw1#

工厂函数必须返回函数

工厂函数必须返回模拟:替代它所嘲笑的东西的物体。
因为我们模拟的是一个ES6类,也就是a function with some syntactic sugar,那么这个模拟本身必须是一个函数,因此传递给jest.mock()的工厂函数必须返回一个函数;换句话说,它必须是高阶函数。
在上面的代码中,factory函数返回一个对象,由于在该对象上调用new失败,所以它不起作用。

可以调用new的简单模拟:

下面是一个简单的版本,因为它返回一个函数,所以允许调用new

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

注意:箭头功能无效

注意,我们的mock不能是一个箭头函数,因为我们不能在Javascript中对箭头函数调用new;这是语言固有的特性。所以这是行不通的

jest.mock('./sound-player', () => {
  return () => { // Does not work; arrow functions can't be called with new
    return { playSoundFile: () => {} };
  };
});

这将引发***类型错误:_soundPlayer2.default不是构造函数***。

跟踪使用情况(监视模拟)

不抛出错误固然很好,但我们可能需要测试构造函数是否使用正确的参数调用。
为了跟踪对构造函数的调用,我们可以用Jest mock函数替换霍夫返回的函数,用jest.fn()创建它,然后用mockImplementation()指定它的实现。

jest.mock('./sound-player', () => {
  return jest.fn().mockImplementation(() => { // Works and lets you check for constructor calls
    return { playSoundFile: () => {} };
  });
});

这将允许我们使用SoundPlayer.mock.calls检查模拟类的使用情况。

监视我们类的方法

模拟类需要提供在测试过程中调用的所有成员函数(示例中为playSoundFile),否则调用不存在的函数时会出错,但我们可能还需要监视对这些方法的调用,以确保它们是使用预期参数调用的。
因为在测试过程中会创建一个新的模拟对象,所以SoundPlayer.playSoundFile.calls对我们没有帮助。为了解决这个问题,我们用另一个模拟函数填充playSoundFile,并在测试文件中存储对同一个模拟函数的引用,这样我们就可以在测试过程中访问它。

let mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
  return jest.fn().mockImplementation(() => { // Works and lets you check for constructor calls
    return { playSoundFile: mockPlaySoundFile }; // Now we can track calls to playSoundFile
  });
});

完整示例

下面是它在测试文件中的外观:

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

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

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(mockPlaySoundFile.mock.calls[0][0]).toEqual(coolSoundFileName);
});
68bkxrlz

68bkxrlz2#

如果您仍然在使用TypeError: ...default is not a constructor并且正在使用TypeScript,请继续阅读。
TypeScript正在翻译您的ts文件,并且您的模块可能是使用ES2015s import. const soundPlayer = require('./sound-player')导入的。因此,创建作为默认值导出的类的示例将如下所示:但是,如果您按照文档中的建议模拟类。

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 
            }   
        })
    }
})

对于命名导入(如import { OAuth2 } from './oauth'),请将default替换为导入的模块名称,在此示例中为OAuth2

jest.mock('./oauth', () => {
    return {
        OAuth2: ... // mock here
    }
})
6jjcrrmo

6jjcrrmo3#

Stone和Santiago帮我完成了这个任务,我只想提一下,另外,我必须在import语句之前添加jest mock函数,如下所示:

jest.mock('bootstrap/dist/js/bootstrap.esm.js', () => {
    return {
        Tooltip: function(init){
            this.init = init;
        }
    }
})

import { newSpecPage } from '@stencil/core/testing';
import { CoolCode } from '../cool-code';

谢谢你的帮助!

a7qyws3x

a7qyws3x4#

如果您已经定义了一个模拟类,您可以使用类似于:

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

代码将执行类似于用MockedRealClass的方法和属性定义替换原始RealClass的方法和属性定义的操作。

mfuanj7w

mfuanj7w5#

这对我很有效:

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

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

然后在测试中,使用jest.fn().mockImplementation(()返回SoundPlayer作为键对象

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

相关问题