Brian F爱
从俄勒冈州波特兰的一位专注yobet英雄联盟于Angular、Web技术和Node.js的谷歌开发专家那里学习。
广告 ·ultimatecourses.com
以正确的方式学习Angular的终极课程

NgRx测试:还原剂

学习如何使用Jest对NgRx reducers进行单元测试。

目标是由于调度行动而将突变视为申请状态。️️

下载

在这篇文章中,我将使用一个演示应用程序。您可以克隆存储库或下载源代码的zip文件:

系列

此帖子是使用JEST测试NGRX的系列的一部分:

此应用程序的堆栈将是:

  • 角6
  • NGRX 6.
  • Jest和angular- Jest -预置模块
  • 茉莉花大理石模块
  • 伪造数据的Faker

Jest测试运行器

正如我上面提到的,这篇文章将使用Jest测试运行器。它比Karma快得多(即使是在headless Chrome上),并且使用了类似jasmine的API。事实上,如果您的大多数测试都是由CLI生成的,那么您可以简单地将Karma替换为Jest。

如果你是新人,用角度看看我的帖子

无操作动作

首先,我们应该断言国家是不是突变为无效或诺沃行动减速器()函数src / app / / user / user.reducer.spec.ts状态

描述“未定义行为”= >{'应该返回默认状态'= >{const行动={类型“等待”}作为任何;const结果=减速机未定义的行动;预计结果成为initialState;};};

让我们回顾一下上面的测试:

  • 在“应该返回默认状态”规范中,我们创建了一个新的行动,他的类型被设置为“noop”。
  • 然后调用减速器()功能指定未定义的对当前状态和诺普一起行动
  • 最后,我们预计结果状态与initialState的默认值状态争论在减速器()功能。

如果我们运行我们的测试,它应该通过:

$NPM.测试

AddUser行动

在示例应用程序中,我指定了一些添加用户的操作src / app / / user / user.actions.ts状态

出口枚举UserActiontypes.{AddUser=“[用户]添加用户”addusersuccess.='[用户]添加用户成功'adduserfail.='[用户]添加用户失败'}

src / app /用户/集装箱/添加/ add.component.ts组成部分调度()AddUser行动。这将切换加载布尔属性在州,和addUser效果src / app /存储/ user / user.effects.ts将举行援助userservice.adduser()方法,它将向我们的API发送一个PUT请求,以创建一个新的用户实体。当请求完成时,我们将分派addusersuccess.行动,或者如果响应是错误(例如500个状态代码),我们会发出adduserfail.行动。

这是一个定义AddUser行动:

出口班级AddUser实施行动{readonly类型=UserActiontypes.AddUser;构造函数公共有效载荷{用户部分的<用户>}{}}

为了有效载荷我们只需定义一个必须具有用户财产,这是一个部分的A.用户

接下来,让我们来看看减速器()执行此操作:

出口函数减速机状态=initialState行动用户名状态{开关行动类型{情况下UserActiontypes.AddUser返回{......状态加载真正的错误未定义的};}}

AddUser派遣行动我们正在更新状态,以便加载布尔值设置为真正的确保错误属性设置为未定义的

这是测试AddUser行动src / app / / user / user.reducer.spec.ts状态文件:

描述'用户减速器'= >{描述“[用户]添加用户”= >{“应该切换加载状态”= >{const行动=AddUser{用户};const结果=减速机initialState行动;预计结果toequal.{......initialState错误未定义的加载真正的};};};};

让我们回顾一下测试:

  • 首先,我们的所有测试都在“用户减速器”测试套件内。
  • 在测试中,我们更新了AddUser操作,指定使用faker创建的生成用户。
  • 然后调用减速器()功能,提供initialState行动
  • 最后,我们预计结果等于一个新的物体;第一次传播initialState对象,然后指定错误加载属性值。

addusersuccess.行动

接下来,让我们断言addusersuccess.操作返回新状态和新创建的用户实体添加。

在此演示应用程序中,我正在使用@ ngrx /实体模块来管理集合用户对象。虽然可能没有必要断言单独使用实体适配器的状态突变,但我想明确测试每个操作,包括那些单独/主要使用适配器方法的操作(例如,附加())对于突变申请的状态。

一般来说,我认为这是安全的:

假设@ ngrx /实体适配器状态突变是经过战斗的,可能在您的应用程序中不需要测试

首先,这里是定义addusersuccess.行动src / app / / user / user.actions.ts状态

出口班级addusersuccess.实施行动{readonly类型=UserActiontypes.addusersuccess.;构造函数公共有效载荷{用户用户}{}}

这里是case语句减速器()addusersuccess.返回新州的行动:

情况下UserActiontypes.addusersuccess.情况下UserActiontypes.LoadUserSuccess{返回适配器addOne行动有效载荷用户{......状态加载错误的};}

正如你所看到的,我们调用附加()方法,提供创建(或加载)的用户,并切换加载布尔属性错误的

现在,让我们看看测试中的测试src / app / / user / user.reducer.spec.ts状态文件:

描述'[用户]添加用户成功'= >{“应该添加一个用户到状态”= >{const行动=addusersuccess.{用户};const结果=减速机initialState行动;预计结果toequal.{......initialState实体{[用户id]用户}ids.[用户id]加载错误的};};};

在这个测试中,我们调用了减速器()函数,指定initialState随着addusersuccess.我们用必要的有效载荷创建的动作。然后,我们期望()结果等于initialState额外的东西实体ids.通过适配器创建的属性附加()方法。

我们还断言加载Boolean属性设置为错误的。这实际上是在减速机中执行的唯一“手动”状态突变值得要测试。

adduserfail.行动

我们还应在添加新用户的HTTP请求失败时测试失败路径。为此,我们会宣传那个adduserfail.行动导致我们申请的新状态错误属性包含错误发生的。

首先,这里是定义adduserfail.行动src / app / / user / user.actions.ts状态

出口班级adduserfail.实施行动{readonly类型=UserActiontypes.adduserfail.;构造函数公共有效载荷{错误错误}{}}

这里是case语句减速器()功能:

情况下UserActiontypes.adduserfail.情况下UserActiontypes.addusersfail.情况下UserActiontypes.LoadUserFail.情况下UserActiontypes.loadUsersfail.情况下UserActiontypes.UpdateUserFail情况下UserActiontypes.UpdateUsersFail{返回{......状态错误行动有效载荷错误加载错误的};}

您将注意到,在这个应用程序中,当任何操作失败时,我们都会得到相同的结果:我们更新错误财产包含错误这是在行动中派出的有效载荷和切换加载布尔到错误的

现在,让我们断言,该状态在适当的时候突变adduserfail.行动派:

描述'[用户]添加用户失败'= >{'应该更新状态中的错误'= >{const错误=错误;const行动=adduserfail.{错误};const结果=减速机initialState行动;预计结果toequal.{......initialState错误加载错误的};};};

这应该开始看起来很害怕。我们加入了adduserfail.行动,援引减速器()功能,并断言结果等于应用程序的预期状态。

LoadUser.LoadUserSuccessLoadUserFail.

测试LoadUser.LoadUserSuccessLoadUserFail.与添加用户的方法几乎相同。让我们快速浏览一下每一个。

断言LoadUser.行动结果等于预期的价值错误属性是未定义的加载财产被切断到真正的

描述“[用户]加载用户”= >{“应该切换加载状态”= >{const行动=LoadUser.{id用户id};const结果=减速机initialState行动;预计结果toequal.{......initialState错误未定义的加载真正的};};};

断言LoadUserSuccess行动结果等于预期的价值实体ids.属性包含在操作中分派的用户有效载荷

描述“[用户]负载用户成功”= >{'应该将用户加载到州'= >{const行动=LoadUserSuccess{用户};const结果=减速机initialState行动;预计结果toequal.{......initialState实体{[用户id]用户}ids.[用户id]加载错误的};};};

断言LoadUserFail.行动结果等于预期的价值错误属性包含错误这是在行动中派出的有效载荷

描述'[用户]加载用户失败'= >{'应该更新状态中的错误'= >{const错误=错误;const行动=LoadUserFail.{错误};const结果=减速机initialState行动;预计结果toequal.{......initialState错误加载错误的};};};

LoadUsersLoadUsersSuccess.loadUsersfail.

单位测试LoadUsersLoadUsersSuccess.loadUsersfail.操作是类似的,除了实体ids.属性更新了许多用户对象。

我们将跳过LoadUsersloadUsersfail.为了简洁的测试,因为它们几乎与之相同LoadUser.LoadUserFail.测试。

让我们快速看一下测试LoadUsersSuccess.行动:

描述“[用户]负载用户成功”= >{'应该将所有用户添加到州'= >{const用户=[用户];const行动=LoadUsersSuccess.{用户};const结果=减速机initialState行动;预计结果toequal.{......initialState实体用户减少entityMap用户= >{......entityMap[用户id]用户}{}ids.用户地图用户= >用户id加载错误的};};};

期望()断言我们使用array.prototype.reduce()方法创建实体字典的用户对象。

@ngrx/entity库使用以下签名来存储实体在状态以便快速检索对象:

出口声明键入diblditnnum.<T.>={[id数字]T.;};出口声明抽象班级字典<T.>实施intermennum.<T.>{[id细绳]T.;}

唯一标识符是否为数字或者一个细绳,唯一标识符是属性名称(或密钥)实体对象。

这就是为什么我们使用减少()方法创建实体匹配的对象intermennum.字典类型。

ids.数组的数组id价值观。我们使用map ()映射值的方法用户数组用户对象。

UpdateUserSuccess.

测试UpdateUserUpdateUserFail行动也与我们查看的现有测试相同,我们正在调度切换的动作加载属性,并更新错误财产。

另一方面,连接一个测试来断言UpdateUserSuccess.Action改变了我们的应用程序的状态,这有点复杂,坦率地说,有点冗长:

描述'用户减速器'= >{const用户用户={id1'Anakin''天空步行者'};描述“[用户]更新用户成功”= >{“应该更新用户的状态”= >{constupdatedUser用户={......用户“黑”“维达”};const行动=UpdateUserSuccess.{更新{id用户id变化updatedUser}};const状态=减速机initialStateaddusersuccess.{用户};预计状态toequal.{......initialState实体{[用户id]用户}ids.[用户id]加载错误的};const结果=减速机状态行动;预计结果toequal.{......状态实体{......状态实体[用户id]updatedUser}ids.[......状态ids.]加载错误的};};};};

这是什么UpdateUserSuccess.测试在做:

  • 首先,我们定义一个updatedUser使用当前的对象用户对象属性修改属性值。我们在这里去了一场星球大战主题。如果您反对星球大战,请与其一起进行此测试。如果你是一个粉丝,希望你能享受乐趣,我们将为我们的角度应用编写单元测试。
  • 然后我们新的一个UpdateUserSuccess.行动与更新财产。再次,我们正在使用@ ngrx /实体库,所以我们指定了一个更新类型签名id变化属性。
  • 我们举行了减速器()提供的功能initialState和一个新的addusersuccess.行动。
  • 我们断言那个addusersuccess.行动适当地返回一个新的状态添加新用户的对象。
  • 然后,我们调动减速器()再次函数。这次我们提供了状态那是一个结果addusersuccess.行动,以及行动的实例UpdateUserSuccess.
  • 最后,我们断言了结果UpdateUserSuccess.行动代表了updatedUser

UpdateUssSuccess.

测试的测试UpdateUssSuccess.Action与我们刚才看到的测试类似UpdateUserSuccess.随着更新多个用户的添加:

描述'用户减速器'= >{const用户用户={id1'Anakin''天空步行者'};描述'[用户]更新用户成功'= >{'应该将所有用户添加到州'= >{const参议员={id2“Sheev”'Palpaatine'};const维德={......用户“黑”“维达”};consts={......参议员“黑”“周围”};const最初的用户=[用户参议员];constupdatedUsers=[维德s];const状态=减速机initialStateAddUsersSuccess{用户最初的用户};const行动=UpdateUssSuccess.{更新[{id用户id变化维德}{id参议员id变化s}]};const结果=减速机状态行动;预计结果toequal.{......状态实体updatedUsers减少entityMap用户= >{......entityMap[用户id]用户}{}ids.updatedUsers地图用户= >用户id加载错误的};};};};

我不会审查每行代码,但一般来说,我们要添加最初的用户到我们的应用程序,然后断言,通过该申请更新多个用户UpdateUssSuccess.动作会导致预期状态。

选择器

最后一个测试src / app / / user / user.reducer.spec.ts状态是断言选择器行动更新选择用户属性在状态对象中:

描述“[用户]选择用户”= >{'应该在状态下设置SelectsUlerId属性= >{const行动=选择器{id用户id};const结果=减速机initialState行动;预计结果toequal.{......initialStateselectedUserid.用户id};};};

运行测试

最后,我们使用测试:

$NPM.测试

我们应该看到所有的测试都通过了。

结论

写作单元测试可以很有趣!☝️

实际上,Angular应用中使用NgRx的单元测试动作可以很容易地完成。在这些示例中,编写更新测试有点繁琐,特别是一次更新多个实体。但是,我们现在有了很好的代码覆盖率☀️,并且可以确保我们的操作通过减速器()功能。

Brian F爱

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