「基於 Jest + Enzyme 的 React 單元測試 | 掘金技術徵文 」

陳惠超發表於2017-04-27

如果你想學習 React 單元測試,那就從這篇文章開始吧。Star 專案,clone 到本地,根據教程走一遍,有任何問題歡迎 issue 討論。

專案GitHub地址:react-test-demo

文章主要內容如下:

  • Jest 和 Enzyme 的基本介紹
  • 測試環境搭建
  • 測試指令碼編寫
    • UI 元件測試
    • Reducer 測試
  • 執行並除錯
  • 參考資料

Jest、Enzyme 介紹

Jest 是 Facebook 釋出的一個開源的、基於 Jasmine 框架的 JavaScript 單元測試工具。提供了包括內建的測試環境 DOM API 支援、斷言庫、Mock 庫等,還包含了 Spapshot Testing、 Instant Feedback 等特性。

Airbnb開源的 React 測試類庫 Enzyme 提供了一套簡潔強大的 API,並通過 jQuery 風格的方式進行DOM 處理,開發體驗十分友好。不僅在開源社群有超高人氣,同時也獲得了React 官方的推薦。

測試環境搭建

在開發 React 應用的基礎上(預設你用的是 Webpack + Babel 來打包構建應用),你需要安裝 Jest Enzyme,以及對應的 babel-jest

npm install jest enzyme babel-jest --save-dev
複製程式碼

下載 npm 依賴包之後,你需要在 package.json 中新增屬性,配置 Jest:

  "jest": {
    "moduleFileExtensions": [
      "js",
      "jsx"
    ],
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
      ".*\\.(css|less|scss)$": "<rootDir>/__mocks__/styleMock.js"
    },
    "transform": {
      "^.+\\.js$": "babel-jest"
    }
  },
複製程式碼

並新增test scripts

"scripts": {
    "dev": "NODE_ENV=development webpack-dev-server  --inline --progress --colors --port 3000 --host 0.0.0.0 ",
    "test": "jest"
  }
複製程式碼

其中 :

  • moduleFileExtensions:代表支援載入的檔名,與 Webpack 中的 resolve.extensions 類似
  • moduleNameMapper:代表需要被 Mock 的資源名稱。如果需要 Mock 靜態資源(如less、scss等),則需要配置 Mock 的路徑 <rootDir>/__mocks__/yourMock.js
  • transform 用於編譯 ES6/ES7 語法,需配合 babel-jest 使用

上面三個是常用的配置,更多 Jest 配置見官方文件:Jest Configuration

測試指令碼編寫

UI 元件測試

環境搭建好了,就可以開始動手寫測試指令碼了。在開始之前,先分析下 Todo 應用的組成部分。

「基於 Jest + Enzyme 的 React 單元測試 | 掘金技術徵文 」

應用主體結構如下 src/component/App.js

class App extends Component {
  render() {
    const { params } = this.props;
    return (
      <section className="todoapp">
        <div className="main">
          <AddTodo />
          <VisibleTodoList filter={params.filter || 'all'} />
        </div>
         <Footer />
      </section>
    )
  }
}
複製程式碼

可以發現 整個應用可以分為三個元件:

  • 最外層的 <App />
  • 中間的 Input 輸入框 <AddTodo />
  • 下面的 TODO 列表 <VisibleTodoList />

其中 <App/> 是 UI 元件,<AddTodo /><VisibleTodoList /> 是智慧元件,我們需要找到智慧元件所對應的 UI 元件 <AddTodoView/><TodoList/>

<AddTodoView/> 就是一個 Input 輸入框,接受文字輸入,敲下Enter鍵,建立一個 Todo。程式碼如下 src/component/AddTodoView.js

import React, { Component, PropTypes } from 'react'
class AddTodoView extends Component {
  render() {
    return (
      <header className="header">
        <h1>todos</h1>
        <input
          className="new-todo"
          type="text"
          onKeyUp={e => this.handleClick(e)}
          placeholder="input todo item"
          ref='input' />
      </header>
    )
  }

  handleClick(e) {
    if (e.keyCode === 13) {
      const node = this.refs.input;
      const text = node.value.trim();
      text && this.props.onAddClick(text);
      node.value = '';
    }
  }
}
複製程式碼

瞭解了該元件的功能之後,我們首先需要明確該元件需要測試哪些點:

  • 元件是否正常渲染
  • 當使用者輸入內容敲下Enter鍵時,是否能正常的呼叫 props 傳遞的 onAddClick(text) 方法
  • 建立完成後清除 Input 的值
  • 當使用者沒有輸入任何值時,敲下回車時,應該不呼叫 props 傳遞的 onAddClick(text) 方法

經過上面的分析之後,我們就可以開始編寫單元測試指令碼了。

第一步:引入相關 lib

import React from 'react'
import App from '../../src/component/App'
import { shallow } from 'enzyme'
複製程式碼

在這裡我們引入了 shallow 方法,它是 Enzyme 提供的 API 之一,可以實現淺渲染。其作用是僅僅渲染至虛擬節點,不會返回真實的節點,能極大提高測試效能。但是它不適合測試包含子元件、需要測試宣告週期的元件。 Enzyme 還提供了其他兩個 API:

  • mount:Full Rendering,非常適用於存在於 DOM API 存在互動元件,或者需要測試元件完整的宣告週期
  • render:Static Rendering,用於 將 React 元件渲染成靜態的 HTML 並分析生成的 HTML 結構。render 返回的 wrapper 與其他兩個 API 類似。不同的是 render 使用了第三方 HTML 解析器和 Cheerio

一般情況下,shallow 就已經足夠用了,偶爾情況下會用到 mount

第二步:模擬 Props,渲染元件建立 Wrapper

這一步,我們可以建立一個 setup 函式來實現。

const setup = () => {
  // 模擬 props
  const props = {
    // Jest 提供的mock 函式
    onAddClick: jest.fn()
  }

  // 通過 enzyme 提供的 shallow(淺渲染) 建立元件
  const wrapper = shallow(<AddTodoView {...props} />)
  return {
    props,
    wrapper
  }
}
複製程式碼

Props 中包含函式的時候,我們需要使用 Jest 提供的 mockFunction

第四步:編寫 Test Case

這裡的 Case 根據我們前面分析需要測試的點編寫。

Case1:測試元件是否正常渲染

describe('AddTodoView', () => {
  const { wrapper, props } = setup();

  // case1
  // 通過查詢存在 Input,測試元件正常渲染
  it('AddTodoView Component should be render', () => {
    //.find(selector) 是 Enzyme shallow Rendering 提供的語法, 用於查詢節點
    // 詳細用法見 Enzyme 文件 http://airbnb.io/enzyme/docs/api/shallow.html
    expect(wrapper.find('input').exists());
  })
})
複製程式碼

寫完第一個測試用例之後,我們可以執行看看測試的效果。在 Terminal 中輸入 npm run test,效果如下:

「基於 Jest + Enzyme 的 React 單元測試 | 掘金技術徵文 」

Case2: 輸入內容並敲下Enter鍵,測試元件呼叫props的方法

  it('When the Enter key was pressed, onAddClick() shoule be called', () => {
    // mock input 輸入和 Enter事件
    const mockEvent = {
      keyCode: 13, // enter 事件
      target: {
        value: 'Test'
      }
    }
    // 通過 Enzyme 提供的 simulate api 模擬 DOM 事件
    wrapper.find('input').simulate('keyup',mockEvent)
    // 判斷 props.onAddClick 是否被呼叫
    expect(props.onAddClick).toBeCalled()
  })
複製程式碼

上面的程式碼與第一個 case 多了兩點:

  • 增加了 mockEvent,用於模擬 DOM 事件
  • 使用 Enzyme 提供的 .simulate(’keyup‘, mockEvent) 來模擬點選事件,這裡的 keyup 會自動轉換成 React 元件中的 onKeyUp 並呼叫。

我們再執行 npm run test 看看測試效果:

「基於 Jest + Enzyme 的 React 單元測試 | 掘金技術徵文 」

經過上面兩個 Test Case 的分析,接下來的 Case3 和 Case4 思路也是一樣,具體寫法見程式碼: test/component/AddTodoView.spec.js,這裡就不一一講解了。

Reducer 測試

由於 Reducer 是純函式,因此對 Reducer 的測試非常簡單,Redux 官方文件也提供了測試的例子,程式碼如下:

import reducer from '../../reducers/todos'
import * as types from '../../constants/ActionTypes'

describe('todos reducer', () => {
  it('should return the initial state', () => {
    expect(
      reducer(undefined, {})
    ).toEqual([
      {
        text: 'Use Redux',
        completed: false,
        id: 0
      }
    ])
  })

  it('should handle ADD_TODO', () => {
    expect(
      reducer([], {
        type: types.ADD_TODO,
        text: 'Run the tests'
      })
    ).toEqual(
      [
        {
          text: 'Run the tests',
          completed: false,
          id: 0
        }
      ]
    )

    expect(
      reducer(
        [
          {
            text: 'Use Redux',
            completed: false,
            id: 0
          }
        ],
        {
          type: types.ADD_TODO,
          text: 'Run the tests'
        }
      )
    ).toEqual(
      [
        {
          text: 'Run the tests',
          completed: false,
          id: 1
        },
        {
          text: 'Use Redux',
          completed: false,
          id: 0
        }
      ]
    )
  })
})

複製程式碼

更多關於 Redux 的測試可以看官網提供的例子:編寫測試-Redux文件

除錯及測試覆蓋率報告

在執行測試指令碼過程,Jest 的錯誤提示資訊友好,通過錯誤資訊一般都能找到問題的所在。 同時 Jest 還提供了生成測試覆蓋率報告的命令,只需要新增上 --coverage 這個引數既可生成。不僅會在終端中顯示:

「基於 Jest + Enzyme 的 React 單元測試 | 掘金技術徵文 」

而且還會在專案中生成 coverage 資料夾,非常方便。

資料

掘金技術徵文第三期:聊聊你的最佳實踐:https://juejin.im/post/58d8e99261ff4b006cd6874d

相關文章