作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
阿隆索·阿亚拉·奥尔特加
验证专家 在工程

在过去十年中, Alonso的Oracle认证和全栈工作最近转向了QA自动化和尖锐的BDD解决方案.

以前在

网飞公司
分享
< div >

根据Michael Feathers的说法,任何没有测试的代码都被称为遗留代码. 因此,避免创建遗留代码的最佳方法之一是使用测试驱动开发(TDD)。.

While t在这里 are many tools available for JavaScript 和 反应.js 单元测试,在这篇文章中,我们将使用开玩笑和酶来创建反应.js 组件 with basic functionality using TDD.

为什么使用TDD来创建反应.js组件?

TDD为您的代码带来了许多好处——高测试覆盖率的一个好处是,它使代码重构变得容易,同时保持代码的干净和功能性.

如果你已经创建了一个反应.Js组件之前,您已经意识到代码可以快速增长. 它充满了由与状态更改和服务调用相关的语句引起的许多复杂条件.

每个缺少单元测试的组件都有难以维护的遗留代码. 我们可以 添加单元测试 after we create the production code. 然而,我们可能会冒着忽视一些应该被测试的场景的风险. 通过先创建测试, 我们有更高的机会覆盖组件中的每个逻辑场景, which would make it easy to refactor 和 maintain.

如何对反应进行单元测试.js组件?

T在这里 are many strategies we can use to 测试 a 反应.js组件:

  • We can verify that a particular function in 道具 was called when certain a 事件被分派.
  • We can also get the result of the 渲染 函数给出当前组件的状态,并将其与预定义的布局相匹配.
  • 我们甚至可以检查组件的子元素的数量是否与预期的数量相匹配.

In order to use these strategies, 我们将使用两个方便的工具来处理反应中的测试.js: 开玩笑.

Using 开玩笑 to Create Unit Tests

开玩笑是一个由Facebook创建的开源测试框架,它与反应有很好的集成.js. 它包括一个用于测试执行的命令行工具,类似于Jasmine和Mocha提供的工具. 它还允许我们创建几乎没有配置的模拟函数,并提供了一组非常好的匹配器,使断言更容易阅读.

此外, it offers a really nice feature called “提前shot 测试ing,” which helps us check 和 verify the 组件 渲染ing result. 我们将使用快照测试来捕获组件的树,并将其保存到一个文件中,以便与渲染树(或传递给组件的任何树)进行比较 预计 函数作为第一个参数.)

使用酶来安装反应.js组件

酶 provides a mechanism to 山 和 traverse 反应.Js组件树. 这将帮助我们访问它自己的属性和状态,以及它的子道具,以便运行我们的断言.

酶 offers two basic functions for 组件 山ing: . 的 function loads in memory only the root 组件 w在这里as 加载完整的DOM树.

We’re going to combine 酶 和 开玩笑 to 山 a 反应.js 组件 和 run assertions over it.

TDD steps to create a react 组件

建立我们的环境

你可以看一下 这种回购, which has the basic configuration to run 这 example.

We’re using the following versions:

{
  “反应”:“16.0.0",
  “酶”:“^ 2.9.1",
  “笑话”:“^ 21.2.1",
  :“jest-cli ^ 21.2.1",
  :“babel-jest ^ 21.2.0"
}

创建反应.使用TDD的组件

第一步是创建一个失败的测试,它将尝试渲染反应.js组件 using the enzyme’s 浅 function.

MyComponent / /添加.测试.js
从“反应”中导入反应;
import { 浅 } from 'enzyme';
导入MyComponent.MyComponent /添加';
describe("MyComponent", () => {
  it("should 渲染 my 组件", () => {
    const wrapper = 浅();
  });
});

After running the 测试, we get the following error:

ReferenceError: MyComponent is not defined.

然后,我们创建提供基本语法的组件,以使测试通过.

MyComponent / /添加.js
从“反应”中导入反应;

export default class MyComponent extends 反应.组件{
  呈现(){
    return 
; } }

在下一步中,我们将确保我们的组件呈现预定义的UI布局,使用 toMatchSnapshot 来自开玩笑的函数.

调用此方法后,开玩笑自动创建一个名为 (测试FileName).提前,其中添加了 __提前shots__ 文件夹.

这个文件代表了我们期望从组件呈现中得到的UI布局.

However, given that we are trying to do TDD, we should create 这 文件 first 和 then call the toMatchSnapshot function to make the 测试 fail.

This may sound a little confusing, 因为我们不知道开玩笑使用哪种格式来表示这个布局.

You may be tempted to execute the toMatchSnapshot 函数,然后在快照文件中查看结果,这是一个有效的选项. However, if we truly want to use TDD, we need to learn how 提前shot 文件s are structured.

快照文件包含与测试名称匹配的布局. This means that if our 测试 has 这 form:

desc("ComponentA" () => {
  it("should do something", () => {
    …
  }
});

We should specify 这 in the exports section: Component A should do something 1.

You can 读 more about 提前shot 测试ing 在这里.

我们首先创建 MyComponent.测试.js.提前 文件.

/ / __提前shots__ MyComponent /添加.测试.js.提前
exports[`MyComponent should 渲染 initial layout 1`] = `
数组(
, ] `;

然后,我们创建单元测试,检查快照是否与组件子元素匹配.

MyComponent / /添加.测试.js
...
it("should 渲染 initial layout", () => {
    / /当
    const 组件 = 浅();
    / /然后
    期望(组件.getElements ()).toMatchSnapshot ();
});
...

我们可以考虑 组件.getElements as the result of the 渲染 method.

将这些元素传递给 预计 method in order to run the verification against the 提前shot 文件.

After executing the 测试 we get the following error:

Received 价值 does not match stored 提前shot 1.
预期:
 -数组[
    
MyComponent / /添加.js 从“反应”中导入反应; export default class MyComponent extends 反应.组件{ 呈现(){ return
; } }

的 next step is to add functionality to input by executing a function when its 价值 changes. We do 这 by specifying a function in the onChange 道具.

We first need to change the 提前shot to make the 测试 fail.

/ / __提前shots__ MyComponent /添加.测试.js.提前
exports[`MyComponent should 渲染 initial layout 1`] = `
数组(
, ] `;

首先修改快照的一个缺点是,道具(或属性)的顺序很重要.

开玩笑 will alphabetically sort the 道具 received in the 预计 function before verifying it against the 提前shot. So, we should specify them in that order.

After executing the 测试 we get the following error:

Received 价值 does not match stored 提前shot 1.
预期:
 -数组[
    
onChange ={[功能]} MyComponent / /添加.js 从“反应”中导入反应; export default class MyComponent extends 反应.组件{ 呈现(){ return
{}} type = " text " />
; } }

的n, we make sure that the 组件’s 状态 changes after the onChange 事件被分派.

To do 这, we create a new unit 测试 which is going to call the onChange function in the input by passing an 事件 in order to mimic a real 事件 in the UI.

的n, we verify that the 组件 状态 包含一个名为 input.

MyComponent / /添加.测试.js
...
it("should create an entry in 组件 状态", () => {
    / /给定
    const 组件 = 浅();
    Const form = 组件.找到(“输入”);
    / /当
    form.道具().onChange({目标:{
       名称:“名字”,
       价值:“括号”
    }});
    / /然后
    期望(组件.状态(“输入”)).toBeDefined ();
});

We now get the following error.

Expected 价值 to be defined, instead received undefined

这表明该组件在被调用的状态中没有属性 input.

我们通过在组件的状态中设置这个条目来使测试通过.

MyComponent / /添加.js
从“反应”中导入反应;

export default class MyComponent extends 反应.组件{
  呈现(){
    return 
{这.设置状态({输入:"}}} type = " text " />
; } }

的n, we need to make sure a 价值 is set in the new 状态 entry. We will get 这 价值 from the 事件.

因此,让我们创建一个测试来确保状态包含这个值.

MyComponent / /添加.测试.js
...
  it("should create an entry in 组件 状态 with the 事件 价值", () => {
    / /给定
    const 组件 = 浅();
    Const form = 组件.找到(“输入”);
    / /当
    form.道具().onChange({目标:{
      名称:“名字”,
      价值:“括号”
    }});
    / /然后
    期望(组件.状态(“输入”)).toEqual(“括号”);
  });
 ~~~

Not surprisingly, we get the following error.

~~
Expected 价值 to equal: "myValue"
收到:“”

最后,我们通过从事件获取值并将其设置为输入值,从而使该测试通过.

MyComponent / /添加.js
从“反应”中导入反应;

export default class MyComponent extends 反应.组件{
  呈现(){
    return 
{ 这.设置状态({输入:事件.目标.值}}} type = " text " />
; } }

After making sure all 测试s pass, we can refactor our code.

We can extract the function passed in the onChange 属性转换为被调用的新函数 updateState.

MyComponent / /添加.js
从“反应”中导入反应;

export default class MyComponent extends 反应.组件{
  updateState(事件){
    这.设置状态({
        输入:事件.目标.价值
    });
  }
  呈现(){
    return 
; } }

现在我们有了一个简单的反应.js 组件 created using TDD.

Summary

In 这 example, we tried to use TDD通过遵循每个步骤编写尽可能少的代码来失败和通过测试.

有些步骤似乎是不必要的,我们可能会跳过它们. However, whenever we skip any step, we’ll end up using a 更少的纯 TDD版本.

使用不那么严格的TDD过程也是有效的,并且可能工作得很好.

我给你的建议是避免跳过任何步骤,如果你觉得困难也不要难过. TDD是一种不容易掌握的技术,但它绝对值得一试.

如果您有兴趣了解更多关于TDD和相关的行为驱动开发(BDD), 读 Your Boss Won’t Appreciate TDD by fellow Toptaler Ryan Wilcox.

了解基本知识

  • What is 测试-driven development?

    测试驱动的开发是基于我们编写测试和生产代码的顺序的软件开发过程. 简而言之, 我们希望将失败所必需的测试代码和通过所必需的生产代码保持在最低限度.

  • 什么是反应组件?

    A 反应 组件 combines logic 和 presentational code. 它主要用于为基于状态和属性变化的web和移动UI组件提供一个抽象层

Hire a Toptal expert on 这 topic.
现在雇佣
阿隆索·阿亚拉·奥尔特加

阿隆索·阿亚拉·奥尔特加

验证专家 在工程

西班牙马拉加

Member since September 18, 2017

作者简介

在过去十年中, Alonso的Oracle认证和全栈工作最近转向了QA自动化和尖锐的BDD解决方案.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

以前在

网飞公司

World-class articles, delivered weekly.

<输入 aria-label="Form Input" class="_1CkBk_k-" name="email" placeholder="Enter your email" required="" type="email" 价值=""/>

Subscription implies consent to our 隐私政策

< div >

World-class articles, delivered weekly.

<输入 aria-label="Form Input" class="_1CkBk_k-" name="email" placeholder="Enter your email" required="" type="email" 价值=""/>

Subscription implies consent to our 隐私政策

Toptal开发者

加入总冠军® 社区.