跳转到主要内容
版本:26.倍

ES6类模拟

Jest可以用来模拟导入到想要测试的文件中的ES6类。

ES6类是带有一些句法糖的构造函数。因此,ES6类的任何模型必须是函数或实际的ES6类(这是再次,另一个功能)。所以你可以使用它们模拟功能

ES6类示例#

我们将使用一个虚构的类例子来播放声音文件,SoundPlayer,以及使用该类的消费者类,SoundPlayerConsumer.。我们会嘲笑SoundPlayer在我们的测试中SoundPlayerConsumer.

/ / sound-player.js
出口 默认的 SoundPlayer {
构造函数 ( ) {
喷火 = '酒吧' ;
}
playsoundfile. ( 文件名 ) {
安慰 日志 ( “播放声音文件” + 文件名 ) ;
}
}
// sound-player-confiecer.js
进口 SoundPlayer “。/留声机” ;
出口 默认的 SoundPlayerConsumer. {
构造函数 ( ) {
SoundPlayer. = 新的 SoundPlayer ( ) ;
}
playSomethingCool ( ) {
常量 coolsoundfilename. = 'song.mp3' ;
SoundPlayer. playsoundfile. ( coolsoundfilename. ) ;
}
}

创建ES6类模拟的4种方法#

自动模拟#

打电话jest.mock(’。/留声机”)返回一个有用的“自动模拟”,可以用来监视对类构造函数及其所有方法的调用。它用模拟构造函数替换ES6类,用模拟功能总是回归不明确的。方法调用保存在theAutomaticMock.mock.instances .methodName.mock.calls(指数)

请注意,如果你在你的类中使用箭头函数,它们将会成为模仿的一部分。原因是箭头函数不存在于对象的原型中,它们只是持有对函数的引用的属性。

如果您不需要替换该类的实现,这是设置设置的最简单选择。举例说明:

进口 SoundPlayer “。/留声机” ;
进口 SoundPlayerConsumer. ”。/ sound-player-consumer ' ;
开玩笑 嘲笑 ( “。/留声机” ) ; // SoundPlayer现在是一个模拟构造函数
beforeEach ( ( ) => {
//清除构造函数和方法的所有实例和调用:
SoundPlayer 笨拙 ( ) ;
} ) ;
( “我们可以检查消费者是否称为类构造函数' , ( ) => {
常量 SoundPlayerConsumer. = 新的 SoundPlayerConsumer. ( ) ;
预计 ( SoundPlayer ) tohavebeencalledtimes. ( 1 ) ;
} ) ;
( 我们可以检查消费者是否在类实例上调用了方法 , ( ) => {
//显示mockClear()工作:
预计 ( SoundPlayer ) TohaveBeencalled. ( ) ;
常量 SoundPlayerConsumer. = 新的 SoundPlayerConsumer. ( ) ;
//应该再次调用//构造函数:
预计 ( SoundPlayer ) tohavebeencalledtimes. ( 1 ) ;
常量 coolsoundfilename. = 'song.mp3' ;
SoundPlayerConsumer. playSomethingCool ( ) ;
/ /模拟。实例可以使用自动模拟:
常量 mockSoundPlayerInstance = SoundPlayer 嘲笑 实例 ( 0 ] ;
常量 mockPlaySoundFile = mockSoundPlayerInstance playsoundfile. ;
预计 ( mockPlaySoundFile 嘲笑 调用 ( 0 ] ( 0 ] ) toEqual ( coolsoundfilename. ) ;
//相当于上述检查:
预计 ( mockPlaySoundFile ) Tohavebeencalledwith. ( coolsoundfilename. ) ;
预计 ( mockPlaySoundFile ) tohavebeencalledtimes. ( 1 ) ;
} ) ;

人工模拟#

创建一个手动模拟控件中保存模拟实现__mocks__文件夹。这允许您指定实现,并且可以跨测试文件使用。

// __mocks __ / sound-player.js
//导入这个命名的导出到你的测试文件:
出口 常量 mockPlaySoundFile = 开玩笑 fn ( ) ;
常量 嘲笑 = 开玩笑 fn ( ) 模仿 ( ( ) => {
返回 { playsoundfile. : mockPlaySoundFile } ;
} ) ;
出口 默认的 嘲笑 ;

导入被所有实例共享的mock和mock方法:

/ / sound-player-consumer.test.js
进口 SoundPlayer , { mockPlaySoundFile } “。/留声机” ;
进口 SoundPlayerConsumer. ”。/ sound-player-consumer ' ;
开玩笑 嘲笑 ( “。/留声机” ) ; // SoundPlayer现在是一个模拟构造函数
beforeEach ( ( ) => {
//清除构造函数和方法的所有实例和调用:
SoundPlayer 笨拙 ( ) ;
mockPlaySoundFile 笨拙 ( ) ;
} ) ;
( “我们可以检查消费者是否称为类构造函数' , ( ) => {
常量 SoundPlayerConsumer. = 新的 SoundPlayerConsumer. ( ) ;
预计 ( SoundPlayer ) tohavebeencalledtimes. ( 1 ) ;
} ) ;
( 我们可以检查消费者是否在类实例上调用了方法 , ( ) => {
常量 SoundPlayerConsumer. = 新的 SoundPlayerConsumer. ( ) ;
常量 coolsoundfilename. = 'song.mp3' ;
SoundPlayerConsumer. playSomethingCool ( ) ;
预计 ( mockPlaySoundFile ) Tohavebeencalledwith. ( coolsoundfilename. ) ;
} ) ;

打电话jest.mock()使用模块出厂参数#

jest.mock(路径,modulefactory)需要一个模块的工厂论点。模块工厂是一个返回模拟的函数。

为了模拟构造函数,模块工厂必须返回一个构造函数。换句话说,模块工厂必须是一个返回一个函数的函数——一个高阶函数(HOF)。

进口 SoundPlayer “。/留声机” ;
常量 mockPlaySoundFile = 开玩笑 fn ( ) ;
开玩笑 嘲笑 ( “。/留声机” , ( ) => {
返回 开玩笑 fn ( ) 模仿 ( ( ) => {
返回 { playsoundfile. : mockPlaySoundFile } ;
} ) ;
} ) ;

与工厂参数的限制是这样,因为呼叫jest.mock()已挂起到文件的顶部,因此不可能首先定义变量,然后在工厂使用它。对于从“Mock”一词开始的变量进行了例外。由您来保证他们将按时初始化!例如,由于使用“假”而不是“变量声明”而不是“Mock”,以下将抛出范围超出错误:

//注意:这将失败
进口 SoundPlayer “。/留声机” ;
常量 fakePlaySoundFile = 开玩笑 fn ( ) ;
开玩笑 嘲笑 ( “。/留声机” , ( ) => {
返回 开玩笑 fn ( ) 模仿 ( ( ) => {
返回 { playsoundfile. : fakePlaySoundFile } ;
} ) ;
} ) ;

使用以下代码替换mockmockImplementation ()或者mockImplementationOnce ()#

您可以通过呼叫来替换所有上述模块以更改单个测试或所有测试的实现mockImplementation ()在现有的mock上。

调用笑话。嘲笑are hoisted to the top of the code. You can specify a mock later, e.g. inbeforeAll (),通过调用mockImplementation ()(或mockImplementationOnce ())在现有模拟而不是使用工厂参数。如果需要,这也允许您更改测试之间的模拟:

进口 SoundPlayer “。/留声机” ;
进口 SoundPlayerConsumer. ”。/ sound-player-consumer ' ;
开玩笑 嘲笑 ( “。/留声机” ) ;
描述 ( "当SoundPlayer抛出错误时" , ( ) => {
beforeAll ( ( ) => {
SoundPlayer 模仿 ( ( ) => {
返回 {
playsoundfile. : ( ) => {
新的 错误 ( '测试错误' ) ;
} ,
} ;
} ) ;
} ) ;
( '打电话给PlaysometionCooh时应抛出错误' , ( ) => {
常量 SoundPlayerConsumer. = 新的 SoundPlayerConsumer. ( ) ;
预计 ( ( ) => SoundPlayerConsumer. playSomethingCool ( ) ) tothrow. ( ) ;
} ) ;
} ) ;

深入:理解模拟构造函数#

建立构造函数模拟使用jest.fn()。模仿()使模拟看起来比实际复杂。本节展示如何创建自己的mock,以说明mock是如何工作的。

手动模拟是另一个ES6类#

类中的被模拟类使用相同的文件名来定义ES6类__mocks__文件夹,它将作为模拟。此类将用于代替真实类。这允许您对类注入测试实现,但不提供呼叫中谍的方法。

对于创意的示例,模拟可能如下所示:

// __mocks __ / sound-player.js
出口 默认的 SoundPlayer {
构造函数 ( ) {
安慰 日志 ( '模拟SoundPlayer:构造函数被称为' ) ;
}
playsoundfile. ( ) {
安慰 日志 ( " Mock SoundPlayer: playSoundFile was called " ) ;
}
}

模块使用模块出厂参数#

传递给的模块工厂函数jest.mock(路径,modulefactory)可以是返回函数*的HOF。这将允许调用新的在模拟。同样,这允许您为测试注入不同的行为,但不提供监视调用的方法。

*模块出厂功能必须返回函数#

为了模拟构造函数,模块工厂必须返回一个构造函数。换句话说,模块工厂必须是一个返回一个函数的函数——一个高阶函数(HOF)。

开玩笑 嘲笑 ( “。/留声机” , ( ) => {
返回 功能 ( ) {
返回 { playsoundfile. : ( ) => { } } ;
} ;
} ) ;

注意:箭头函数不起作用

请注意,模拟不能是箭头函数,因为调用新的在JavaScript中,箭头函数是不允许的。所以这行不通:

开玩笑 嘲笑 ( “。/留声机” , ( ) => {
返回 ( ) => {
//不能工作;箭头函数不能用new调用
返回 { playsoundfile. : ( ) => { } } ;
} ;
} ) ;

这将把_soundPlayer2.default不是构造函数,除非代码被编译为ES5,例如by@babel / preset-env。(ES5没有箭头函数也没有类,所以两者都将被编译为普通函数。)

跟踪用法轨道(在模拟中窥探)#

注入测试实现是有帮助的,但您可能还想测试是否以正确的参数调用类构造函数亚搏取款和方法。

监视建筑工人#

为了跟踪对构造函数的调用,替换Hof返回的函数与Jest模拟功能。创建它jest.fn(),然后指定其实现mockImplementation ()

进口 SoundPlayer “。/留声机” ;
开玩笑 嘲笑 ( “。/留声机” , ( ) => {
//工作并允许您检查构造函数调用:
返回 开玩笑 fn ( ) 模仿 ( ( ) => {
返回 { playsoundfile. : ( ) => { } } ;
} ) ;
} ) ;

这将让我们检查我们的模拟类的使用,使用SoundPlayer.mock.calls:期待(SoundPlayer).tohavebeencalled();或near-equivalent:期望(SoundPlayer.mock.calls.length) .toEqual (1);

模拟非默认类导出#

如果这个类从模块中导出的默认导出,那么您需要将对象返回与类导出名称相同的键。

进口 { SoundPlayer } “。/留声机” ;
开玩笑 嘲笑 ( “。/留声机” , ( ) => {
//工作并允许您检查构造函数调用:
返回 {
SoundPlayer : 开玩笑 fn ( ) 模仿 ( ( ) => {
返回 { playsoundfile. : ( ) => { } } ;
} ) ,
} ;
} ) ;

窥探我们班级的方法#

我们的模拟类将需要提供任何成员函数(playsoundfile.在我们的测试过程中会调用的),否则我们将会得到一个调用不存在的函数的错误。但是,我们可能还想监视对这些方法的调用,以确保它们是用预期的参数调用的。

每次测试期间调用Mock构造函数函数时,将创建一个新对象。我们填充了所有这些对象的方法调用playsoundfile.使用另一个模拟功能,并在测试文件中存储对同一模拟功能的引用,因此在测试期间可用。

进口 SoundPlayer “。/留声机” ;
常量 mockPlaySoundFile = 开玩笑 fn ( ) ;
开玩笑 嘲笑 ( “。/留声机” , ( ) => {
返回 开玩笑 fn ( ) 模仿 ( ( ) => {
返回 { playsoundfile. : mockPlaySoundFile } ;
//现在我们可以跟踪对playsoundfile的调用
} ) ;
} ) ;

手动模拟相当于这将是:

// __mocks __ / sound-player.js
//将此命名导出导入测试文件
出口 常量 mockPlaySoundFile = 开玩笑 fn ( ) ;
常量 嘲笑 = 开玩笑 fn ( ) 模仿 ( ( ) => {
返回 { playsoundfile. : mockPlaySoundFile } ;
} ) ;
出口 默认的 嘲笑 ;

用法类似于模块工厂函数,只是可以省略from的第二个参数jest.mock(),并且您必须将模拟方法导入测试文件,因为它不再在那里定义。使用原始模块路径;不包括__mocks__

测试之间的清理#

要清除对模拟构造函数函数及其方法的调用记录,我们调用mockClear ()在里面beforeEach ()功能:

beforeEach ( ( ) => {
SoundPlayer 笨拙 ( ) ;
mockPlaySoundFile 笨拙 ( ) ;
} ) ;

完整的例子#

下面是一个完整的测试文件,它使用模块工厂参数来jest.mock:

/ / sound-player-consumer.test.js
进口 SoundPlayer “。/留声机” ;
进口 SoundPlayerConsumer. ”。/ sound-player-consumer ' ;
常量 mockPlaySoundFile = 开玩笑 fn ( ) ;
开玩笑 嘲笑 ( “。/留声机” , ( ) => {
返回 开玩笑 fn ( ) 模仿 ( ( ) => {
返回 { playsoundfile. : mockPlaySoundFile } ;
} ) ;
} ) ;
beforeEach ( ( ) => {
SoundPlayer 笨拙 ( ) ;
mockPlaySoundFile 笨拙 ( ) ;
} ) ;
( “消费者应该能够在SoundPlayer上调用新() , ( ) => {
常量 SoundPlayerConsumer. = 新的 SoundPlayerConsumer. ( ) ;
//确保构造函数创建对象:
预计 ( SoundPlayerConsumer. ) toBeTruthy ( ) ;
} ) ;
( “我们可以检查消费者是否称为类构造函数' , ( ) => {
常量 SoundPlayerConsumer. = 新的 SoundPlayerConsumer. ( ) ;
预计 ( SoundPlayer ) tohavebeencalledtimes. ( 1 ) ;
} ) ;
( 我们可以检查消费者是否在类实例上调用了方法 , ( ) => {
常量 SoundPlayerConsumer. = 新的 SoundPlayerConsumer. ( ) ;
常量 coolsoundfilename. = 'song.mp3' ;
SoundPlayerConsumer. playSomethingCool ( ) ;
预计 ( mockPlaySoundFile 嘲笑 调用 ( 0 ] ( 0 ] ) toEqual ( coolsoundfilename. ) ;
} ) ;