【全棧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來編寫後續測試的。閱讀的速度要快得多, 並且更容易辨別實際發生的事情。

明天, 咱們將繼續咱們的測試旅程和經過集成測試測試咱們的應用。

圖片描述

相關文章
相關標籤/搜索