NgRx测试:影响
学习如何使用Jest单元测试NgRx效果。
下载
在这篇文章中,我将使用一个演示应用程序。您可以克隆存储库或下载源代码的zip文件:
系列
本文是使用Jest测试NgRx系列文章的一部分:
堆栈
此应用程序的堆栈将是:
- 角6
- NgRx 6
- Jest和angular的Jest预置模块
- jasmine-marbles模块
- 伪造伪造数据
Jest测试运行器
正如我上面提到的,这篇文章将使用Jest测试运行器。它比Karma快得多(即使使用无头铬),并且使用类似jasmine的API。事实上,如果您的大多数测试是由CLI生成的,那么您可以简单地将Karma换成Jest。
如果你是笑话新手,<一个href="//www.nxtmastery.com/2018/05/26/angular-jest-testing/">看看我关于在Angular中使用Jest的文章。
jasmine-marbles
我们将使用jasmine-marbles模块,以模拟可观察对象。
这里有一些链接到更多关于用NgRx测试大理石的信息:
总之,我们可以使用类似大理石图的字符串来描述一个随时间变化的可观察流。这使我们能够同步地测试异步可观察流。
我们将使用两个主要函数:
热()
创建一个热的可观察流。冷()
创建一个冷的可观察流。
我们将使用这些函数断言应用程序中特定操作的结果将产生我们所描述的预期的热或冷可观察流。
大理石测试使用一个字符串来描述可观察流:
-
一个破折号表示一段虚拟的时间,也就是10毫秒,但这并不是很重要。a-z0-9
任何字母数字字符都表示一个值。我们可以使用第二个参数热()
和冷()
函数来指定每个标记所代表的值。()
对数将值组合到一个单独的帧中。^
胡萝卜表示一个冰冷的可观察流中订阅的开始。|
管道表示完成通知。#
井号(或哈希符号)表示错误通知。
设置
第一步是得到试验台
设置。我们将使用模拟函数jest.fn ()
以及使用TestActions
类,以便可以修改可观测的
动作的来源。
示例中的所有代码都位于演示应用程序中src / app / / user / user.effects.spec.ts状态文件。
出口类TestActions扩展行动{构造函数(){超级(空());}集流(源:可观测的<任何>){这。源=源;}}出口函数getActions(){返回新TestActions();}描述(“UserEffects”,()= >{让行动:TestActions;让影响:UserEffects;让userService:UserService;beforeEach(()= >{试验台。configureTestingModule({供应商:(UserEffects,{提供:行动,useFactory:getActions},{提供:UserService,useValue:{addUser:开玩笑。fn(),getUser:开玩笑。fn(),getuser:开玩笑。fn(),updateUser:开玩笑。fn()}}]});行动=试验台。得到(行动);影响=试验台。得到(UserEffects);userService=试验台。得到(UserService);});它(“创建”,()= >{预计(影响)。toBeTruthy();});});
让我们回顾一下:
- 首先,我们创建
TestActions
类扩展行动
类在@ngrx /影响。类添加了一个赋值器(setter)流
财产。 - 我们还定义了
getActions ()
类的新实例TestActions
类。我们将把它作为工厂函数提供给Angular的依赖注入试验台
代替行动
类中的UserEffects
类。 - 使用
beforeEach ()
方法,我们连接试验台
,提供了UserEffects
类,我们在本例中要测试的。我们也模仿UserService
使用jest.fn ()
。这些只是占位符,我们将在测试中根据需要覆盖它们。 - 然后我们使用
TestBed.get ()
方法来存储对行动
,UserEffects
和UserService
实例。 - 最后,我们有标准的“它应该被创建”测试,以确保我们的effects类被正确地实例化。
现在我们可以开始为每种效果编写单元测试了src / app / / user / user.effects.ts状态。
addUser
效果
首先,让我们看看addUser
我们将测试的效果:
出口类UserEffects{@效果()addUser:可观测的<行动>=这。美元的行为。减低<AddUser>(UserActionTypes。AddUser)。管(地图(行动= >行动。有效载荷),exhaustMap(有效载荷= >这。userService。addUser(有效载荷。用户)),地图(用户= >新AddUserSuccess({用户})),catchError(错误= >的(新AddUserFail({错误}))));构造函数(私人美元的行为:行动,私人userService:UserService){}}
让我们快速总结一下addUser
效果:
- 我们使用
减低()
方法来筛选特定的所有操作AddUser
行动。 - 然后,我们
管()
这个可观察对象有几个运算符。 - 首先,我们
map ()
返回的可观察流数据有效载荷
对象。 - 其次,我们使用
exhaustMap ()
转换到可观察流的操作符addUser ()
方法UserService ()
,指定用户
在有效载荷
。 - 第三,我们
map ()
到AddUserSuccess
操作为有效负载对象提供所需的用户
财产。 - 最后我们使用
catchError ()
操作符捕获异常,返回一个新的可观察对象AddUserFail
行动。 - 然后分派效果产生的操作。
好的,让我们创建一个测试套件addUser
效果:
描述(“addUser”,()= >{它(应该返回一个AddUserSuccess动作,与用户,在成功,()= >{常量用户=generateUser();常量行动=新AddUser({用户});常量结果=新AddUserSuccess({用户});行动。流=热(“——”,{一个:行动});常量响应=冷(“——|”,{一个:用户});常量预期=冷(“——b”,{b:结果});userService。addUser=开玩笑。fn(()= >响应);预计(影响。addUser)。toBeObservable(预期);});它('失败时应该返回一个AddUserFail动作,并带一个错误',()= >{常量用户=generateUser();常量行动=新AddUser({用户});常量错误=新错误();常量结果=新AddUserFail({错误});行动。流=热(“——”,{一个:行动});常量响应=冷(”——# |”,{},错误);常量预期=冷(“——(b |)”,{b:结果});userService。addUser=开玩笑。fn(()= >响应);预计(影响。addUser)。toBeObservable(预期);});});
我们的测试套件由两个测试组成。首先,我们断言成功路径;这一AddUserSuccess
作为效果的结果,行动被分派。其次,我们断言失败路径;这一AddUserFail
action作为一个异常的结果被分派。
让我们深入了解成功之路的细节:
- 首先,我们调用
generateUser ()
创建一个新的用户
对象使用伪造者。 - 然后,我们重新开始
AddUser
行动,指定有效载荷
对象,具有必需的用户
财产。 - 我们也更新了
AddUserSuccess
行动。 - 然后,我们设置
流
内的财产TestActions
类。回忆,行动
常量变量从试验台
在我们嘲笑了行动
注入到UserEffects
类。我们设置了流
一个测试热的可观察对象,它在一个帧之后发出一个下一个
通知的行动
。这基本上设置了要在行动
NgRx中的可观察流。 - 我们定义的
响应
常量作为一个冷观察对象,它在帧之后发出一个通知用户
然后在第三帧发出一个完成通知。 - 我们定义的
预期
常量作为一个冷的观察对象,它在两次成名后放出结果
这是AddUserSuccess
我们期待的行动。 - 使用
jest.fn ()
mock我们覆盖addUser
的方法UserService
返回响应
- 最后,
期望()
这一addUser
效果会导致一个可观察对象预期
热可观测。
我们还测试了失败路径:
- 首先我们使用
generateUser
函数生成一个假的用户
对象。 - 然后我们重新开始
AddUser
和AddUserFail
行动。注意,我们创建了一个新的错误
对象的冷可观察对象流,以便在创建时使用它响应
。 - 我们设置了
流
财产的行动
的实例TestActions
类中注入的试验台
。 - 请注意,
响应
是一个冷观察,它在帧后发出一个错误通知(#
井号),然后是完成通知|
管道符号)。 - 的
预期
可观察到的是冷的可观察到的,在两帧之后,它发出AddUserFail
操作下一步通知和完成通知。注意,时间是在next和completion通知之前的两帧。这是因为我们模拟了之前的可观察对象流
包括单个帧和可观察对象响应
流也分别包括动作和错误通知之前的一个时间帧。因此,在两帧虚拟时间之后预期
可观察对象同时发出AddUserFail
操作和完成通知。 - 就像在之前的测试中一样,我们模仿
addUser
方法来返回一个可观察对象响应
。 - 最后,我们
期望()
这一addUser
效果会产生一个匹配的observable预期
热可观测。
loadUsers
效果
在我们测试之前loadUsers
效果,让我们快速看一下效果:
@效果()loadUsers:可观测的<行动>=这。美元的行为。减低<LoadUsers>(UserActionTypes。LoadUsers)。管(exhaustMap(()= >这。userService。getuser()),地图(用户= >新LoadUsersSuccess({用户})),catchError(错误= >的(新LoadUsersFail({错误}))));
当我们看这个效果时,有很多相似之处addUser
的效果。一般来说,我们用UserService
检索所有用户,然后映射到LoadUsersSuccess
行动。如果出了什么差错,我们就派出LoadUsersFail
行动。
下面是成功和失败路径的测试套件loadUsers
效果:
描述(“loadUsers”,()= >{它(在成功时,应该返回LoadUsersSuccess操作,()= >{常量用户=generateUsers();常量行动=新LoadUsers();常量结果=新LoadUsersSuccess({用户:用户});行动。流=热(“——”,{一个:行动});常量响应=冷(“——|”,{一个:用户});常量预期=冷(“——b”,{b:结果});userService。getuser=开玩笑。fn(()= >响应);预计(影响。loadUsers)。toBeObservable(预期);});它('失败时应该返回一个LoadUsersFail动作,并带一个错误',()= >{常量行动=新LoadUsers();常量错误=新错误();常量结果=新LoadUsersFail({错误:错误});行动。流=热(“——”,{一个:行动});常量响应=冷(”——# |”,{},错误);常量预期=冷(“——(b |)”,{b:结果});userService。getuser=开玩笑。fn(()= >响应);预计(影响。loadUsers)。toBeObservable(预期);});});
这与以前的成功和失败路径测试也应该是相似的addUser
的效果。主要的区别是不同的操作和这些操作所需的有效载荷。同样,我们编写了两个测试,就像我们为addUser
效果:一个用于成功路径,另一个用于失败路径。
的loadUser
测试套件也非常相似。为了简洁起见,我将跳过它,但是你们可以随意浏览一下测试src / app / / user / user.effects.spec.ts状态
updateUser
效果
这是updateUser
我们将测试的效果:
@效果()updateUser:可观测的<行动>=这。美元的行为。减低<UpdateUser>(UserActionTypes。UpdateUser)。管(地图(行动= >行动。有效载荷),exhaustMap(有效载荷= >这。userService。updateUser(有效载荷。用户)),地图(用户= >新UpdateUserSuccess({更新:{id:用户。id,变化:用户}})),catchError(错误= >的(新UpdateUserFail({错误}))));
在updateUser
我们过滤所有特定的动作UpdateUser
行动,然后使用exhaustMap ()
函数返回的可观察对象UserService.updateUser ()
实例方法。我们map ()
成功的秘诀UpdateUserSuccess
行动是由效果发出的,或者我们catchError ()
返回一个可观察对象的()
的UpdateUserFail
行动。
的测试套件updateUser
效果:
描述(“updateUser”,()= >{它(在成功时,应该返回一个UpdateUserSuccess动作,()= >{常量用户=generateUser();常量行动=新UpdateUser({用户});常量结果=新UpdateUserSuccess({更新:{id:用户。id,变化:用户}});行动。流=热(“——”,{一个:行动});常量响应=冷(“——|”,{一个:用户});常量预期=冷(“——b”,{b:结果});userService。updateUser=开玩笑。fn(()= >响应);预计(影响。updateUser)。toBeObservable(预期);});它('在失败时应该返回一个UpdateUserFail操作,并带一个错误',()= >{常量用户=generateUser();常量行动=新UpdateUser({用户});常量错误=新错误();常量结果=新UpdateUserFail({错误});行动。流=热(“——”,{一个:行动});常量响应=冷(”——# |”,{},错误);常量预期=冷(“——(b |)”,{b:结果});userService。updateUser=开玩笑。fn(()= >响应);预计(影响。updateUser)。toBeObservable(预期);});});
我们测试成功和失败的路径,设置必要的可观察对象,并断言结果可观察对象就是真实的预期
。
结论
使用jasmine-marbles模块,我们可以通过使用虚拟计时器(调度器)和模拟值和动作,轻松地测试使用同步代码的异步效果。