使用 Jest 和 Enzyme 進行 React Native 單元測試|技術點評

你們好,我是 @大寧的洛竹html

本文首發於 洛竹的官方網站前端

本文已受權掘金開發者社區公衆號獨家使用,包括但不限於編輯、標註原創等權益。react

單元測試是什麼 🧐

其中單元測試是用來對一個模塊、一個函數或者一個類來進行正確性檢驗的測試工做。程序單元是應用的最小可測試部件,在 React 編程中,最小單元一般是組件、函數。若是你熟悉「測試驅動開發」(TDD:Test-Driven Development),單元測試也不會陌生,狹義來講就是單測驅動開發。jquery

一般來講,程序員每修改一次程序就會進行最少一次單元測試,在編寫程序的過程當中先後極可能要進行屢次單元測試,以證明程序達到軟件規格書要求的工做目標,沒有程序錯誤。在 TDD 中,甚至是先根據設計編寫單元測試,而後根據單元測試寫代碼。ios

萬丈高樓平地起,單元測試和文檔同樣,是保障程序最小單元質量的重要一環。試想一下,一塊磚可能不須要使用說明書就能夠量產使用,可是一塊磚不經質檢測驗就投入使用帶來的後果多是恐怖的。從這個角度來看,單測多是比文檔更重要的存在。固然咱們也不提倡爲了單測而單測,單測是爲了防範於未然。git

其餘測試

前端測試常見的測試類型有單元測試(Unit testing)、集成測試(Integration testing)、端到端(E2E testing)測試,通常咱們投入的測試資源排序以下:程序員

集成測試是在單元測試的基礎上,集成多個模塊進行測試,確保模塊之間互動行爲正確無誤的工做。有時,單一的模塊徹底經過單元測試,單獨使用也沒有問題,可是當與其餘模塊配合使用時,可能就出現問題了,下圖是未經過集成測試的例子:github

端到端測試是站在用戶角度出發(一端)到真實運行環境(另外一端)進行測試。通常咱們會使用 Cypress、puppeteer 這些工具進行自動化測試以替代人肉測試。下圖是未經過端到端測試的例子:shell

測試覆蓋率

咱們在測試的時候,會常常關心咱們的代碼是否都測試到了,以及哪些代碼沒有測試到。jest 內置了 Istanbul 測試覆蓋率工具,咱們能夠經過四個維度的覆蓋率來了解代碼測試覆蓋率狀況:編程

  • Statements(stmts):表達式覆蓋率,是否是每一個表達式都執行了?
  • Branches(Branch):分支覆蓋率,是否是每一個 if 代碼塊都執行了?
  • Functions(Funcs):函數覆蓋率,是否是每一個函數都調用了?
  • Lines(Lines):行覆蓋率,是否是每一行都執行了?

下圖是執行 jest --coverage 以後生成的命令行輸出:

下圖是生成的精美的測試覆蓋率報告:

點擊 App.js 能夠查看單個文件的測試覆蓋率狀況:

點開每一個也沒你,你會看到頁面是五光十色的,別擔憂,這些顏色都是有明確的意義:

  • 粉紅色的代碼: 還沒有被執行的 statement 或 function
  • 黃色的代碼: 沒被覆蓋到的 branch
  • I: 表明 if-else 的 if 沒有被執行

  • E: 表明 if-else 的 if 沒有被執行

  • Nx: 表明代碼塊被執行到的次數,能夠做爲代碼性能的參考依據

安裝依賴

$ yarn add jest -D
# babel
$ yarn add babel-jest -D
# enzyme
$ yarn add enzyme jest-enzyme enzyme-adapter-react-16 enzyme-to-json -D
# react-native-mock-render
$ yarn add react-native-mock-render -D
# types
$ yarn add @types/enzyme @types/jest @types/react @types/react-native -D
複製代碼

工具介紹:

  • jest: Jest 是一個使人愉快的 JavaScript 測試框架,專一於簡潔明快。
  • enzyme: Enzyme 是用於 React 的 JavaScript 測試實用程序,能夠更輕鬆地測試 React 組件的輸出。您還能夠根據給定的輸出進行操做,遍歷並以某種方式模擬運行時。
  • jest-enzyme: 針對 enzyme 的 Jest 斷言
  • enzyme-adapter-react-16: React Native 測試所需的橋接器
  • enzyme-to-json: 將 Enzyme wrappers 轉換成符合 Jest 快照測試的 JSON 格式。
  • react-native-mock-render: A fully mocked and test-friendly version of react native

配置

jest.config.js

module.exports = {
  preset: 'react-native',
  verbose: true,
  collectCoverage: true,
  moduleNameMapper: {
    // for https://github.com/facebook/jest/issues/919
    '^image![a-zA-Z0-9$_-]+$': 'GlobalImageStub',
    '^[@./a-zA-Z0-9$_-]+\\.(png|gif)$': 'RelativeImageStub',
  },
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  snapshotSerializers: ['enzyme-to-json/serializer'],
};
複製代碼
  • collectCoverage: 生成測試覆蓋率報告
  • setupFilesAfterEnv: 使用 Jest 運行安裝文件以配置 Enzyme 和適配器(以下文jest.setup.js中所示),以前是setupTestFrameworkScriptFile,也可使用setupFiles
  • snapshotSerializers:推薦使用序列化程序使用enzyme-to-json,它的安裝和使用很是簡單,並容許您編寫簡潔的快照測試。

注意:Jest 在 24.1.0 以後只能使用 setupFilesAfterEnv

jest.setup.js

import 'react-native';
import 'react-native-mock-render/mock';
import 'react-native/Libraries/Animated/src/bezier'; // for https://github.com/facebook/jest/issues/4710
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });
複製代碼

enzyme 入門

enzyme 是 Airbnb 開源的 react 測試類庫,提供了一套簡潔強大的 API,並經過 jquery 風格的方式進行 dom 處理,開發體驗十分友好. 它提供三種測試方法.

shallow

shallow 返回組件的淺渲染,對官方 shallow rendering 進行封裝。淺渲染 做用就是:它僅僅會渲染至虛擬 dom,不會返回真實的 dom 節點,這個對測試性能有極大的提高。shallow 只渲染當前組件,只能能對當前組件作斷言

mount

mount 方法用於將 React 組件加載爲真實 DOM 節點。mount 會渲染當前組件以及全部子組件。多數狀況下,shallow 方法就能知足咱們的需求了。ref 測試則旨在 mount 模式下生效。

render

render 採用的是第三方庫 Cheerio 的渲染,渲染結果是普通的 html 結構,對於 snapshot 使用 render 比較合適。

組件測試

組件快照測試

當咱們要確保 UI 不會意外更改時,快照測試都是很是有用的工具。經過 toMatchSnapshot 便可完成。

describe('Button Component', () => {
  it('basic render', () => {
    const component = renderer.create(<Button />).toJSON();
    expect(component).toMatchSnapshot();
  });
});
複製代碼

生命週期測試

componentDidMount

經過調用 shallowmount 方法,能夠觸發 componentDidMount 生命週期:

import { shallow } from 'enzyme';

function setup(props = {}) {
  const wrapper = shallow(<CarouselComponent />);
  const instance = wrapper.instance();
  return { wrapper, instance };
}

describe('Carousel Component', () => {
  it('renders correctly', () => {
    setup();
  });
});
複製代碼

也能夠經過 wrapper.setState 方法進行觸發:

import { shallow } from 'enzyme';

function setup(props = {}) {
  const wrapper = shallow(<Component {...props} />);
  const instance = wrapper.instance();
  return { wrapper, instance };
}

describe('Component', () => {
  it('renders correctly', () => {
    const { wrapper } = setup();
    wrapper.setState({
      enable: true,
    });
  });
});
複製代碼

componentWillUnMont

經過調用 wrapper.unmount() 能夠觸發 componentWillUnMont 生命週期:

import { shallow } from 'enzyme';

function setup(props = {}) {
  const wrapper = shallow(<Component />);
  const instance = wrapper.instance();
  return { wrapper, instance };
}
describe('Carousel Component', () => {
  it('renders correctly', () => {
    const { wrapper } = setup();
    expect(wrapper).toMatchSnapshot();
    wrapper.unmount();
    expect(wrapper).toMatchSnapshot();
  });
});
複製代碼

componentWillReceiveProps

能夠經過 wrapper.setProps 方法觸發:

import { shallow } from 'enzyme';

function setup(props = {}) {
  const wrapper = shallow(<Component {...props} />);
  const instance = wrapper.instance();
  return { wrapper, instance };
}

it('componentWillReceiveProps', () => {
  const { wrapper, instance } = setup({
    autoplay: true,
  });
  wrapper.setProps({ autoplay: false });
});
複製代碼

定時器模擬(Timer Mocks)

原生定時器功能(即 setTimeout,setInterval,clearTimeout,clearInterval)對於測試環境來講不太理想,由於它們依賴於實時時間。 Jest 能夠將定時器換成容許咱們本身控制時間的功能。

這裏咱們經過調用 jest.useFakeTimers() 來啓用假定時器。而後在須要的時候執行 jest.runOnlyPendingTimers() 來觸發定時器:

import { shallow } from 'enzyme';

jest.useFakeTimers();

it('autoplay methods with count(2) and os(ios)', () => {
  const { wrapper, instance } = setup({
    autoplay: true,
    loop: false,
  });
  wrapper.setState({ isScrolling: true }, () => {
    jest.runOnlyPendingTimers();
  });
});
複製代碼

FAQ

如何忽略某一塊代碼

添加如下格式的註釋到要忽略的代碼塊前便可:

/* istanbul ignore next */
複製代碼

使用 mount 時,忽略 React Native 的警告

describe('mounting', () => {
    const origConsole = console.error;
    beforeEach(() => {
      console.error = () => {};
    });
    afterEach(() => {
      console.error = origConsole;
    });
    it ......
       mount....
});
複製代碼

常見 issues

相關文章
相關標籤/搜索