4

本文转载自:众成翻译
译者: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', () => {
  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')

  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, 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 />) // 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', () => {
  let wrapper;
  // ...
  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')
    // ...
  })
})

关于第二个测试的棘手部分是, 我们需要点击图标元素。在我们看如何做到这一点之前, 让我们先找到它。我们可以在包装上的目标通过它的 .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', () => {
  it('has 4 status updates at minimum', () => {
    wrapper = shallow(<Timeline />)
    expect(
      wrapper.find('ActivityItem').length
    ).toBeGreaterThan(3)
  })
})

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

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)
    })
  })

})

[](#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来编写后续测试的。阅读的速度要快得多, 而且更容易辨别实际发生的事情。

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

图片描述


不想成熟的大叔
882 声望526 粉丝

为学习前端开发不再枯燥、困难和迷茫而努力。