布莱恩F爱
向一位来自波特兰的专注于Ayobet英雄联盟ngular、Web技术和Node.js的谷歌开发专家学习。
广告 ·ultimatecourses.com
用终极课程正确地学习Angular

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 ()方法来存储对行动,UserEffectsUserService实例。
  • 最后,我们有标准的“它应该被创建”测试,以确保我们的effects类被正确地实例化。

现在我们可以开始为每种效果编写单元测试了src / app / / user / user.effects.ts状态

addUser效果

首先,让我们看看addUser我们将测试的效果:

出口UserEffects{@效果()addUser:可观测的<行动>=美元的行为减低<AddUser>(UserActionTypesAddUser)(地图(行动= >行动有效载荷),exhaustMap(有效载荷= >userServiceaddUser(有效载荷用户)),地图(用户= >AddUserSuccess({用户})),catchError(错误= >(AddUserFail({错误}))));构造函数(私人美元的行为:行动,私人userService:UserService){}}

让我们快速总结一下addUser效果:

  • 我们使用减低()方法来筛选特定的所有操作AddUser行动。
  • 然后,我们管()这个可观察对象有几个运算符。
  • 首先,我们map ()返回的可观察流数据有效载荷对象。
  • 其次,我们使用exhaustMap ()转换到可观察流的操作符addUser ()方法UserService (),指定用户有效载荷
  • 第三,我们map ()AddUserSuccess操作为有效负载对象提供所需的用户财产。
  • 最后我们使用catchError ()操作符捕获异常,返回一个新的可观察对象AddUserFail行动。
  • 然后分派效果产生的操作。

好的,让我们创建一个测试套件addUser效果:

描述(“addUser”,()= >{(应该返回一个AddUserSuccess动作,与用户,在成功,()= >{常量用户=generateUser();常量行动=AddUser({用户});常量结果=AddUserSuccess({用户});行动=(“——”,{一个:行动});常量响应=(“——|”,{一个:用户});常量预期=(“——b”,{b:结果});userServiceaddUser=开玩笑fn(()= >响应);预计(影响addUser)toBeObservable(预期);});('失败时应该返回一个AddUserFail动作,并带一个错误',()= >{常量用户=generateUser();常量行动=AddUser({用户});常量错误=错误();常量结果=AddUserFail({错误});行动=(“——”,{一个:行动});常量响应=(”——# |”,{},错误);常量预期=(“——(b |)”,{b:结果});userServiceaddUser=开玩笑fn(()= >响应);预计(影响addUser)toBeObservable(预期);});});

我们的测试套件由两个测试组成。首先,我们断言成功路径;这一AddUserSuccess作为效果的结果,行动被分派。其次,我们断言失败路径;这一AddUserFailaction作为一个异常的结果被分派。

让我们深入了解成功之路的细节:

  • 首先,我们调用generateUser ()创建一个新的用户对象使用伪造者。
  • 然后,我们重新开始AddUser行动,指定有效载荷对象,具有必需的用户财产。
  • 我们也更新了AddUserSuccess行动。
  • 然后,我们设置内的财产TestActions类。回忆,行动常量变量从试验台在我们嘲笑了行动注入到UserEffects类。我们设置了一个测试热的可观察对象,它在一个帧之后发出一个下一个通知的行动。这基本上设置了要在行动NgRx中的可观察流。
  • 我们定义的响应常量作为一个冷观察对象,它在帧之后发出一个通知用户然后在第三帧发出一个完成通知。
  • 我们定义的预期常量作为一个冷的观察对象,它在两次成名后放出结果这是AddUserSuccess我们期待的行动。
  • 使用jest.fn ()mock我们覆盖addUser的方法UserService返回响应
  • 最后,期望()这一addUser效果会导致一个可观察对象预期热可观测。

我们还测试了失败路径:

  • 首先我们使用generateUser函数生成一个假的用户对象。
  • 然后我们重新开始AddUserAddUserFail行动。注意,我们创建了一个新的错误对象的冷可观察对象流,以便在创建时使用它响应
  • 我们设置了财产的行动的实例TestActions类中注入的试验台
  • 请注意,响应是一个冷观察,它在帧后发出一个错误通知(#井号),然后是完成通知|管道符号)。
  • 预期可观察到的是冷的可观察到的,在两帧之后,它发出AddUserFail操作下一步通知和完成通知。注意,时间是在next和completion通知之前的两帧。这是因为我们模拟了之前的可观察对象包括单个帧和可观察对象响应流也分别包括动作和错误通知之前的一个时间帧。因此,在两帧虚拟时间之后预期可观察对象同时发出AddUserFail操作和完成通知。
  • 就像在之前的测试中一样,我们模仿addUser方法来返回一个可观察对象响应
  • 最后,我们期望()这一addUser效果会产生一个匹配的observable预期热可观测。

loadUsers效果

在我们测试之前loadUsers效果,让我们快速看一下效果:

@效果()loadUsers:可观测的<行动>=美元的行为减低<LoadUsers>(UserActionTypesLoadUsers)(exhaustMap(()= >userServicegetuser()),地图(用户= >LoadUsersSuccess({用户})),catchError(错误= >(LoadUsersFail({错误}))));

当我们看这个效果时,有很多相似之处addUser的效果。一般来说,我们用UserService检索所有用户,然后映射到LoadUsersSuccess行动。如果出了什么差错,我们就派出LoadUsersFail行动。

下面是成功和失败路径的测试套件loadUsers效果:

描述(“loadUsers”,()= >{(在成功时,应该返回LoadUsersSuccess操作,()= >{常量用户=generateUsers();常量行动=LoadUsers();常量结果=LoadUsersSuccess({用户:用户});行动=(“——”,{一个:行动});常量响应=(“——|”,{一个:用户});常量预期=(“——b”,{b:结果});userServicegetuser=开玩笑fn(()= >响应);预计(影响loadUsers)toBeObservable(预期);});('失败时应该返回一个LoadUsersFail动作,并带一个错误',()= >{常量行动=LoadUsers();常量错误=错误();常量结果=LoadUsersFail({错误:错误});行动=(“——”,{一个:行动});常量响应=(”——# |”,{},错误);常量预期=(“——(b |)”,{b:结果});userServicegetuser=开玩笑fn(()= >响应);预计(影响loadUsers)toBeObservable(预期);});});

这与以前的成功和失败路径测试也应该是相似的addUser的效果。主要的区别是不同的操作和这些操作所需的有效载荷。同样,我们编写了两个测试,就像我们为addUser效果:一个用于成功路径,另一个用于失败路径。

loadUser测试套件也非常相似。为了简洁起见,我将跳过它,但是你们可以随意浏览一下测试src / app / / user / user.effects.spec.ts状态

updateUser效果

这是updateUser我们将测试的效果:

@效果()updateUser:可观测的<行动>=美元的行为减低<UpdateUser>(UserActionTypesUpdateUser)(地图(行动= >行动有效载荷),exhaustMap(有效载荷= >userServiceupdateUser(有效载荷用户)),地图(用户= >UpdateUserSuccess({更新:{id:用户id,变化:用户}})),catchError(错误= >(UpdateUserFail({错误}))));

updateUser我们过滤所有特定的动作UpdateUser行动,然后使用exhaustMap ()函数返回的可观察对象UserService.updateUser ()实例方法。我们map ()成功的秘诀UpdateUserSuccess行动是由效果发出的,或者我们catchError ()返回一个可观察对象的()UpdateUserFail行动。

的测试套件updateUser效果:

描述(“updateUser”,()= >{(在成功时,应该返回一个UpdateUserSuccess动作,()= >{常量用户=generateUser();常量行动=UpdateUser({用户});常量结果=UpdateUserSuccess({更新:{id:用户id,变化:用户}});行动=(“——”,{一个:行动});常量响应=(“——|”,{一个:用户});常量预期=(“——b”,{b:结果});userServiceupdateUser=开玩笑fn(()= >响应);预计(影响updateUser)toBeObservable(预期);});('在失败时应该返回一个UpdateUserFail操作,并带一个错误',()= >{常量用户=generateUser();常量行动=UpdateUser({用户});常量错误=错误();常量结果=UpdateUserFail({错误});行动=(“——”,{一个:行动});常量响应=(”——# |”,{},错误);常量预期=(“——(b |)”,{b:结果});userServiceupdateUser=开玩笑fn(()= >响应);预计(影响updateUser)toBeObservable(预期);});});

我们测试成功和失败的路径,设置必要的可观察对象,并断言结果可观察对象就是真实的预期

结论

使用jasmine-marbles模块,我们可以通过使用虚拟计时器(调度器)和模拟值和动作,轻松地测试使用同步代码的异步效果。

布莱恩F爱

嗨,我是布莱恩。我对TypeScript、Angular和Node.js感兴趣。我和我最好的朋友邦妮结婚了,我住在波特兰,我经常滑雪。