【全栈React】第25天: 使用Enzyme做更好的测试

本文转载自:众成翻译
译者:iOSDevLog
链接:http://www.zcfy.cc/article/3806
原文:https://www.fullstackreact.com/30-days-of-react/day-25/

今天,我们将看看一个由Airbnb所维护的开源库,名为Enzyme,使得测试变得简单易用。

昨天我们使用了react-addons-test-utils 库来编写我们对Timeline 组件的第一个测试。但是,此库是相当低级的,使用起来可能有点麻烦。Enzyme是由 AirBnb 团队发布和维护的测试实用程序库,它提供了一个更好的、高级的 API 来处理测试中的React组件。

我们在测试我们的 <Timeline />组件:

使用Enzyme

我们将使用Enzyme,使这些测试更容易写和更可读。

昨天,我们写了我们的第一个测试如下:

import React from 'react';
import TestUtils from 'react-addons-test-utils';

import Timeline from '../Timeline';

describe('Timeline',() => {

  it('wraps content in a div with .notificationsFrame class',() => {
    const wrapper = TestUtils.renderIntoDocument(<Timeline />);
    TestUtils
      .findRenderedDOMComponentWithClass(wrapper,'notificationsFrame');
  });

})

虽然这是可行的,但它不是世界上最容易阅读的测试。当用Enzyme我们重写它时让我们看看这个测试的样子。

我们可以只测试组件的输出,而不是用Enzyme来测试完整的组件树。将不渲染任何组件的子级。这称为 渲染。

Enzyme使浅渲染超容易。我们将使用Enzyme导出的shallow 函数来装载我们的组件。

让我们更新src/components/Timeline/__tests__/Timeline-test.js 文件,包括从 enzyme导入shallow 函数:

import React from 'react';
import { shallow } from 'enzyme';

describe('Timeline',() => {
  it('wraps content in a div with .notificationsFrame class',() => {
    // our tests
  });
})

react-addons-test-utils也支持浅渲染。事实上,Enzyme素只是包装这个函数。虽然昨天我们没有使用浅渲染,但如果我们使用它看起来像这样:

> const renderer = ReactTestUtils.createRenderer();
> renderer.render(<Timeline />)
> const result = renderer.getRenderOutput(); 
>

现在,为了渲染我们的组件,我们可以使用shallow 方法并将结果存储在一个变量中。然后,我们将为在其虚拟 dom 中渲染的不同的React元素 (HTML 或子组件) 查询 渲染的组件。

整个断言包括两行:

import React from 'react';
import { shallow,mount } from 'enzyme';

import Timeline from '../Timeline';

describe('Timeline',() => {
  let wrapper;

  it('wraps content in a div with .notificationsFrame class',() => {
    wrapper = shallow(<Timeline />);
    expect(wrapper.find('.notificationsFrame').length).toEqual(1);
  });

  it('has a title of Timeline',() => {
    wrapper = mount(<Timeline />)
    expect(wrapper.find('.title').text()).toBe("Timeline")
  })

  describe('search button',() => {
    let search;
    beforeEach(() => wrapper = mount(<Timeline />))
    beforeEach(() => search = wrapper.find('input.searchInput'))

    it('starts out hidden',() => {  
      expect(search.hasClass('active')).toBeFalsy()
    })
    it('becomes visible after being clicked on',() => {
      const icon = wrapper.find('.searchIcon')
      icon.simulate('click')
      expect(search.hasClass('active')).toBeTruthy()
    })
  })

  describe('status updates',() => {
    it('has 4 status updates at minimum',() => {
      wrapper = shallow(<Timeline />)
      expect(
        wrapper.find('ActivityItem').length
      ).toBeGreaterThan(3)
    })
  })

})

我们可以使用yarn test命令 (或 npm test 命令) 一样的方式运行测试:

yarn test

我们的测试通过,并且更易于阅读和维护。

让我们继续写断言,从我们昨天开始的假设列表中抽取。我们将首先构建我们的测试套件的其余部分,写出我们的describeit 块。我们将填写的规格与断言后:

import React from 'react';
import { shallow } from 'enzyme';

import Timeline from '../Timeline';

describe('Timeline',() => {
    wrapper = shallow(<Timeline />);
    expect(wrapper.find('.notificationsFrame').length).toEqual(1);
  });

  it('has a title of Timeline')

  describe('search button',() => {
    it('starts out hidden')
    it('becomes visible after being clicked on')
  })

  describe('status updates',() => {
    it('has 4 status updates at minimum')
  })

})

如果我们遵循测试驱动开发 (简称 TDD),我们将首先编写这些假设,然后构建组件以通过这些测试。

让我们填写这些测试,以便它们通过我们现有的Timeline 组件。

我们的标题测试比较简单。我们将查找标题元素并确认标题为Timeline

我们希望标题可以在 .title类下使用。因此,要在规范中使用 .title 类,我们只需使用Enzyme所暴露的find 函数即可获取组件。

因为我们的Header组件是 Timeline 组件的子组件,所以不能使用shallow() 方法。相反,我们必须使用Enzyme提供的 mount() 方法。

Shallow? Mount?

shallow() 渲染函数只渲染我们专门测试的组件,它不会渲染子元素。相反,我们将不得不mount() 组件,因为子组件Header 不可用的 jsdom,否则。

我们将在本文的末尾看到更多的Enzyme函数。

现在让我们填写标题描述:

import React from 'react';
import { shallow,() => {
    wrapper = mount(<Timeline />) // notice the `mount`
    expect(wrapper.find('.title').text()).toBe("Timeline")
  })
})

运行我们的测试,我们将看到这两个期望通过:

接下来,让我们更新我们的搜索按钮测试。我们在这里有两个测试,其中一个要求我们测试一个交互。Enzyme为处理相互作用提供了一个非常干净的界面。让我们来看看如何根据搜索图标编写测试。

同样,由于我们在时间轴中对子元素进行测试,因此我们必须mount() 元素。因为我们要在一个嵌套的describe()块中编写两个测试,所以我们可以在帮助器之前编写一个新的 mount() 来为每个测试重新创建,这样它们是纯的。

此外,我们还将使用 input.searchInput 元素进行两个测试,因此,让我们在前面的帮助器中为该元素编写.find()

describe('Timeline',() => {
  let wrapper;
  // ...
  describe('search button',() => {
    let search;
    beforeEach(() => wrapper = mount(<Timeline />))
    beforeEach(() => search = wrapper.find('input.searchInput'))
    // ...
  })
})

若要测试是否隐藏了搜索输入,我们只需要知道是否应用了active 类。Enzyme为我们提供了一种使用 hasClass() 方法检测组件是否有类的方法。让我们填写第一个测试,期望搜索输入没有活动类:

describe('Timeline',() => {  
      expect(search.hasClass('active')).toBeFalsy()
    })
    it('becomes visible after being clicked on')
    // ...
  })
})

关于第二个测试的棘手部分是,我们需要点击图标元素。在我们看如何做到这一点之前,让我们先找到它。我们可以在包装上的目标通过它的 .searchIcon 类定位到它。

it('becomes visible after being clicked on',() => {
  const icon = wrapper.find('.searchIcon')
})

现在,我们有了图标,我们想模拟一个点击元素。回想一下,onClick() 方法实际上只是浏览器事件的门面。即,单击一个元素只是一个通过组件冒泡的事件。而不是控制鼠标或调用元素上的click,我们将模拟发生在它上的事件。对我们来说,这将是click 事件。

我们将在icon 上使用simulate() 方法来创建此事件:

it('becomes visible after being clicked on',() => {
  const icon = wrapper.find('.searchIcon')
  icon.simulate('click')
})

现在我们可以设定一个search 组件具有active 类的期望。

it('becomes visible after being clicked on',() => {
  const icon = wrapper.find('.searchIcon')
  icon.simulate('click')
  expect(search.hasClass('active')).toBeTruthy()
})

我们对Timeline 组件的最后期望是至少有四状态更新。当我们将这些元素放置在Timeline 组件上时,我们可以 "浅" 渲染组件。此外,由于每个元素都是自定义组件,因此我们可以搜索'ActivityItem'类型的特定组件的列表。

describe('status updates',() => {
  it('has 4 status updates at minimum',() => {
    wrapper = shallow(<Timeline />)
    // ... 
  })
})

现在,我们可以测试ActivityItem 组件列表的长度。我们将设定我们的期望,如果长度至少是4的名单。

describe('status updates',() => {
    wrapper = shallow(<Timeline />)
    expect(
      wrapper.find('ActivityItem').length
    ).toBeGreaterThan(3)
  })
})

我们现在的整个测试套件如下所示:

import React from 'react';
import { shallow,() => {
      wrapper = shallow(<Timeline />)
      expect(
        wrapper.find('ActivityItem').length
      ).toBeGreaterThan(3)
    })
  })

})

[](#whats-the-deal-with-find)find()处理什么?

在我们结束今天之前,我们应该看看一个Enzyme"渲染的界面 (在我们的测试中,wrapper 的对象)。Enzyme文档 太棒了,所以我们要保持这个简短。

基本上,当我们使用find() 函数时,我们会将它传递给一个选择器,它将返回一个ShallowWrapper 实例来包装找到的节点。find() 函数可以取字符串、函数或对象。

当我们将字符串传递给find()函数时,我们可以传递 CSS 选择器或组件的 _显示名称_。例如:

wrapper.find('div.link');
wrapper.find('Link')

我们还可以将它传递给组件构造函数,例如:

import { Link } from 'react-router';
// ...
wrapper.find(Link)

最后,我们还可以传递对象属性选择器对象,它通过键和值来选择元素。例如:

wrapper.find({to: '/login'});

返回值是一个 ShallowWrapper,它是一种ShallowWrapper类型 (我们可以渲染包装和浅包装)。这些 Wrapper 实例有一组功能,我们可以使用这些函数来针对不同的子组件,查看 propsstate,的方法,以及渲染的组件的其他属性,如html()text()。更甚的是,我们可以把这些调用串在一起。

<Link />组件为例。如果我们想找到基于所有可用链接的链接类的 HTML,我们可以编写这样的测试:

// ...
it('displays a link tag with the Login text',() => {
  link = wrapper
        .find('Link')
        .find({to: '/login'})

  expect(link.html())
    .toBe('<a class="link">Login</a>')
});

哦!今天有很多新的信息,但是看看我们是如何快速地用Enzyme来编写后续测试的。阅读的速度要快得多,而且更容易辨别实际发生的事情。

明天,我们将继续我们的测试旅程和通过集成测试测试我们的应用。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom