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

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

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

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

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

使用Enzyme

咱们将使用Enzyme, 使这些测试更容易写和更可读。浏览器

昨天, 咱们写了咱们的第一个测试以下:react-router

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咱们重写它时让咱们看看这个测试的样子。app

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

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

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

图片描述

相关文章
相关标签/搜索