「技術雷達」之使用 Enzyme 測試 React(Native)組件

本文同步發表於我的博客:使用 Enzyme 測試 React(Native)組件 - 呂立青的博客javascript

組件化與 UI 測試

在組件化出現以前,咱們不談 UI 的單元測試,哪怕是對於 UI 頁面的測試來講都是一件很是困難的事情。其實組件化並不全是爲了複用,不少狀況下也偏偏是爲了分治,從而咱們能夠分組件對 UI 頁面進行開發,而後分別對其進行單元測試。html

特別是當瀏覽器中的 Web 應用愈來愈龐大的時候,借鑑於在後端將大型單體應用拆分紅微服務架構的最佳實踐同樣,前端應用也能夠被拆分紅不一樣的頁面和特性。每一個特性由一個單獨的團隊從端到端對其負責,它容許團隊規模化地交付那些可以獨立部署和維護的服務,在最新一期的技術雷達當中這種方式稱之爲微前端,微前端的目標就是容許 Web 應用的特性之間彼此獨立,每一個特性能夠獨立地開發、測試和部署。前端

React.js 做爲前端框架的後起之秀,卻在 2015 年攜着虛擬 DOM,組件化,單向數據流等利器,給前端 UI 構建掀起了一波聲勢浩大的函數式新潮流。雖說組件化不是 React 最早提出來的,但倒是 React 使得組件化在前端世界裏發揚光大的,而如今幾乎全部的所謂現代化 UI 框架好比 Angular 或者 Vue 都已經將組件化做爲框架的立足之本。java

React 已經讓 UI 測試變得容易不少,React 組件均可以被簡化爲這樣一個表達式,即 UI = f(data),這個純函數返回的只是一個描述 UI 組件應該是什麼樣子的虛擬 DOM,本質上就是一個樹形的數據結構。給這個純函數輸入一些應用程序的狀態,就會獲得相應的 UI 描述的輸出,這個過程不會去直接操做實際的 UI 元素,也不會產生所謂的反作用。node

React 組件樹的測試

按理來講按照純函數這樣的思路,React 組件的測試應該很簡單的說。但與此同時對於(渲染出 UI 的)組件樹進行測試依然存在一個問題,從下圖中能夠看出,越處於上層的組件,其複雜度必然會隨之提升。對於最底層的子組件來講,咱們能夠很容易得將其進行渲染並測試其邏輯的正確與否,但對於較上層的父組件來講,一般來講就須要對其所包含的全部子組件都進行預先渲染,甚至於最上面的組件須要渲染出整個 UI 頁面的真實 DOM 節點才能對其進行測試,這顯然是不可取的。react

Components-Tree

Shallow rendering lets you render a component "one level deep" and assert facts about what its render method returns, without worrying about the behavior of child components, which are not instantiated or rendered. This does not require a DOM.git

淺渲染(Shallow Rendering)解決了這個問題,也就是說在咱們針對某個上層組件進行測試時,能夠不用渲染它的子組件,因此就不用再擔憂子組件的表現和行爲,這樣就能夠只對特定組件的邏輯及其渲染輸出進行測試了。Facebook 官方提供了 react-addons-test-utils 可讓咱們使用淺渲染這個特性,用於測試虛擬 DOM 對象,即 React.Component 的實例。github

使用 Enzyme 簡化測試代碼

咱們經常會提到,測試代碼對於複雜代碼庫的可維護性相當重要,可是測試的代碼自己的易於理解和編寫,以及可讀性和可維護性也同等重要。後端

Enzyme is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components' output.react-native

而 Enzyme 則來自於活躍在 JavaScript 開源社區的 Airbnb 公司,是對官方測試工具庫(react-addons-test-utils)的封裝,它模擬了 jQuery 的 API,很是直觀而且易於使用和學習,提供了一些不同凡響的接口和幾個方法來減小測試的樣板代碼,方便你判斷、操縱和遍歷 React Components 的輸出,而且減小了測試代碼和實現代碼之間的耦合。Enzyme 理論上應該與全部 TestRunner 和斷言庫相兼容,已經集成了多種測試類庫,好比 Jest,Mocha & Chai,或者 Jasmine,不過這些不是咱們今天的重點。

對比一下二者 facebook/react-addons-test-utils vs airbnb/enzyme 的 API 就一目瞭然,立見分明:

Enzyme 的三種渲染方法

shallow(node[, options]) => ShallowWrapper

shallow 方法就是對官方的 Shallow Rendering 的封裝,淺渲染在將一個組件做爲一個單元進行測試的時候很是有用,能夠確保你的測試不會去間接斷言子組件的行爲。shallow 方法只會渲染出組件的第一層 DOM 結構,其嵌套的子組件不會被渲染出來,從而使得渲染的效率更高,單元測試的速度也會更快。

import { shallow } from 'enzyme'

describe('Enzyme Shallow', () => {
  it('App should have three <Todo /> components', () => {
    const app = shallow(<App />) expect(app.find('Todo')).to.have.length(3) }) }複製代碼

mount(node[, options]) => ReactWrapper

mount 方法則會將 React 組件渲染爲真實的 DOM 節點,特別是在你依賴真實的 DOM 結構必須存在的狀況下,好比說按鈕的點擊事件。徹底的 DOM 渲染須要在全局範圍內提供完整的 DOM API, 這也就意味着它必須在至少「看起來像」瀏覽器環境的環境中運行,若是不想在瀏覽器中運行測試,推薦使用 mount 的方法是依賴於一個名爲 jsdom 的庫,它本質上是一個徹底在 JavaScript 中實現的 headless 瀏覽器。

import { mount } from 'enzyme'

describe('Enzyme Mount', () => {
  it('should delete Todo when click button', () => {
    const app = mount(<App />) const todoLength = app.find('li').length app.find('button.delete').at(0).simulate('click') expect(app.find('li').length).to.equal(todoLength - 1) }) })複製代碼

render(node[, options]) => CheerioWrapper

render 方法則會將 React 組件渲染成靜態的 HTML 字符串,返回的是一個 Cheerio 實例對象,採用的是一個第三方的 HTML 解析庫 Cheerio,官方的解釋是「咱們相信 Cheerio 能夠很是好地處理 HTML 的解析和遍歷,再重複造輪子只能算是一種損失」。這個 CheerioWrapper 能夠用於分析最終結果的 HTML 代碼結構,它的 API 跟 shallow 和 mount 方法的 API 都保持基本一致。

import { render } from 'enzyme'

describe('Enzyme Render', () => {
  it('Todo item should not have todo-done class', () => {
    const app = render(<App />)
    expect(app.find('.todo-done').length).to.equal(0)
    expect(app.contains(<div className="todo" />)).to.equal(true)
  })
})複製代碼

Enzyme 的 API 方法

find() 方法與選擇器

從前面的示例代碼中能夠看到,不管哪一種渲染方式所返回的 wrapper 都有一個 .find() 方法,它接受一個 selector 參數,而後返回一個類型相同的 wrapper 對象,裏面包含了全部符合條件的子組件。在這個對象的基礎上,at 方法則能夠返回指定位置的子組件,simulate 方法能夠在這個組件上模擬觸發某種行爲。

Enzyme 中的 Selectors 即選擇器相似於 CSS 選擇器,可是隻支持很是簡單的 CSS 選擇器,若是須要支持複雜的 CSS 選擇器,就須要引入 react-dom 模塊的 findDOMNode 方法,而這是官方的 TestUtils 自己都不提供的方式。

/* CSS Selector */
wrapper.find('.foo') //class syntax
wrapper.find('input') //tag syntax
wrapper.find('#foo') //id syntax 
wrapper.find('[htmlFor="foo"]') //prop syntax複製代碼

Selectors 也能夠是許多其餘的東西,以便於在 Enzyme 的 wrapper 中能夠輕鬆地指定想要查找的節點,在下面的示例中,咱們能夠經過 React 組件構造函數的引用找到該組件,也能夠基於 React 的 displayName 來查找組件,若是一個組件存在於渲染樹中,其中設置了 displayName 而且它的第一個字符爲大寫字母,就能經過字符串找到它,與此同時也能夠基於 React 組件屬性的子集來查找組件和節點。

/* Component Constructor */
wrapper.find(ChildrenComponent)
myComponent.displayName = 'ChildrenComponent'
wrapper.find('ChildrenComponent')

/* Object Property Selector */
const wrapper = mount(
  <div> <span foo={3} bar={false} title="baz" /> </div> ) wrapper.find({ foo: 3 }) wrapper.find({ bar: false }) wrapper.find({ title: 'baz'})複製代碼

測試組件的交互行爲

咱們不但能夠經過 find 方法查找 DOM 元素,還能夠經過 simulate 方法在組件上模擬觸發某個 DOM 事件,好比 Click,Change 等等。對於淺渲染來講,事件模擬並不會像真實環境中所預期的那樣進行傳播,所以咱們必須在一個已經設置好了事件處理方法的實際節點上纔可以調用,實際上 .simulate() 方法將會根據模擬的事件觸發這個組件的 prop。例如,.simulate('click') 實際上會獲取 onClick prop 並調用它。

Sinon 則是一個能夠用來 Mock 和 Stub 數據代碼的第三方測試工具庫,當咱們須要檢查一個組件當中某個特定的函數是否被調用時,咱們可使用 sinon.spy() 方法監視所傳入該組件做爲 prop 的 onButtonClick 方法,而後再經過 wrapper 的 simulate 方法模擬一個 Click 事件,最終驗證這個被 spy 的 onButtonClick 函數是否被調用。

it('simulates click events', () => {  
  const onButtonClick = sinon.spy()
  const wrapper = shallow(
    <Foo onButtonClick={onButtonClick} /> ) wrapper.find('button').simulate('click') expect(onButtonClick.calledOnce).to.be.true })複製代碼

如何測試 React Native?

前面咱們所談論的都是如何測試使用 react-dom 所構建的 React 組件,即最終渲染的結果是瀏覽器當中的 DOM 結構,但對於 React Native 來講,JavaScript 代碼最終會被編譯並用於調用 iOS 或 Android 上的 Native 代碼,所以沒法再使用基於 DOM 的測試工具了。與此同時,React Native 還有特別多的 Mobile 環境依賴,因此在沒有真實設備的狀況下很難對其運行環境進行模擬,特別是當你但願在持續集成服務器(如 Jenkins、Travis CI)運行單元測試的時候。

事實上,咱們能夠經過欺騙 React Native 讓它返回常規的 React 組件而不是 Native 組件,而後就又能愉快地使用傳統的 JavaScript 測試庫來單獨測試 React Native 組件邏輯。最基本的 mock 示例代碼以下:

const mockComponent = (type) => {
  return React.createClass({
    displayName: type,
    propTypes: {
      children: React.PropTypes.node
    },
    render() {
      return <div {...this.props}>{this.props.children}</div>
    }
  })
}

RN.View = mockComponent("View")
RN.Text = mockComponent("Text")
RN.Image = mockComponent("Image")複製代碼

Enzyme 推薦在測試環境中使用 react-native-mock 這個輔助庫,這是一個使用純 JavaScript 將所有的 React Native 組件進行 mock 的第三方庫,只須要導入這個庫就能夠對 React Native 組件進行渲染和測試。

總結

技術雷達:咱們很是享受Enzyme爲React.js應用提供的快速組件級UI測試功能。與許多其餘基於快照的測試框架不一樣,Enzyme容許開發者在不進行設備渲染的狀況下作測試,從而實現速度更快,粒度更小的測試。在開發React應用時,咱們常常須要作大量的功能測試,而Enzyme能夠在大規模地減小功能測試數量上作出貢獻。

TechRadar
相關文章
相關標籤/搜索