React Native 應用程序中的單元測試

已發表: 2022-03-10
快速總結 ↬單元測試已經成為軟件開發過程中不可或缺的一部分。 它是測試軟件組件的測試級別。 在本教程中,您將學習如何測試 React Native 應用程序的單元。

React Native 是用於構建移動應用程序的最廣泛使用的框架之一。 本教程面向希望開始測試他們構建的 React Native 應用程序的開發人員。 我們將使用 Jest 測試框架和 Enzyme。

在本文中,我們將學習測試的核心原則,探索用於測試應用程序的各種庫,並了解如何測試 React Native 應用程序的單元(或組件)。 通過使用 React Native 應用程序,我們將鞏固我們的測試知識。

注意:在學習本教程時,JavaScript 和 React Native 的基本知識會大有裨益。

什麼是單元測試?

單元測試是測試軟件各個組件的測試級別。 我們這樣做是為了確保每個組件都按預期工作。 組件是軟件中最小的可測試部分。

為了說明,讓我們創建一個Button組件並模擬一個單元測試:

 import React from 'react'; import { StyleSheet, Text, TouchableOpacity } from 'react-native'; function AppButton({ onPress }) { return ( <TouchableOpacity style={[styles.button, { backgroundColor: colors[color] }]} onPress={onPress} > <Text style={styles.text}>Register</Text> </TouchableOpacity> ); } const styles = StyleSheet.create({ button: { backgroundColor: red; borderRadius: 25, justifyContent: 'center', alignItems: 'center', }, text: { color: #fff } }) export default AppButton;

Button組件具有文本和onPress功能。 讓我們測試這個組件,看看單元測試是關於什麼的。

首先,讓我們創建一個名為Button.test.js的測試文件:

 it('renders correctly across screens', () => { const tree = renderer.create(<Button />).toJSON(); expect(tree).toMatchSnapshot(); });

在這裡,我們正在測試我們的Button組件是否在應用程序的所有屏幕上都按應有的方式呈現。 這就是單元測試的全部內容:測試應用程序的組件以確保它們按應有的方式工作。

React Native 應用程序中的單元測試

可以使用多種工具測試 React Native 應用程序,其中一些工具如下:

  • 網絡驅動程序
    這個用於 Node.js 應用程序的開源測試工具也用於測試 React Native 應用程序。
  • 惡夢
    這會自動執行瀏覽器中的測試操作。 根據文檔,“目標是公開一些模仿用戶操作的簡單方法(如gototypeclick ),並使用一個對每個腳本塊感覺同步的 API,而不是深度嵌套的回調。”
  • 笑話
    這是目前最流行的測試庫之一,也是我們今天要關注的。 與 React 一樣,它由 Facebook 維護,旨在提供“零配置”設置以實現最佳性能。
  • 摩卡
    Mocha 是一個流行的庫,用於測試 React 和 React Native 應用程序。 它已成為開發人員首選的測試工具,因為它易於設置和使用以及速度有多快。
  • 茉莉花
    根據其文檔,Jasmine 是一個用於測試 JavaScript 代碼的行為驅動開發框架。
跳躍後更多! 繼續往下看↓

Jest 和 Enzyme 簡介

根據其文檔,“Jest 是一個令人愉悅的 JavaScript 測試框架,專注於簡單性”。 它適用於零配置。 安裝後(使用 npm 或 Yarn 等包管理器),Jest 即可使用,無需其他安裝。

Enzyme 是一個用於 React Native 應用程序的 JavaScript 測試框架。 (如果您使用的是 React 而不是 React Native,則可以使用指南。)我們將使用 Enzyme 來測試我們應用程序輸出的單元。 有了它,我們可以模擬應用程序的運行時。

讓我們開始設置我們的項目。 我們將使用 GitHub 上的 Done With It 應用程序。 這是一個 React Native 應用市場。 首先克隆它,導航到文件夾,然後通過為 npm 運行以下命令來安裝包...

 npm install

……或者這個對於紗線:

 yarn install

此命令將安裝我們應用程序中的所有包。 完成後,我們將使用快照測試應用程序的 UI 一致性,如下所述。

快照和 Jest 配置

在本節中,我們將通過使用 Jest 測試快照來測試用戶觸摸和應用程序組件的 UI。

在此之前,我們需要安裝 Jest 及其依賴項。 要為 Expo React Native 安裝 Jest,請運行以下命令:

 yarn add jest-expo --dev

這會在我們的應用程序目錄中安裝jest-expo 。 接下來,我們需要更新我們的package.json文件以擁有一個測試腳本:

 "scripts": { "test" "jest" }, "jest": { "preset": "jest-expo" }

通過添加這個命令,我們告訴 Jest 在我們的應用程序中註冊哪個包以及在哪裡註冊。

接下來是向我們的應用程序添加其他包,以幫助 Jest 進行全面測試。 對於 npm,運行這個…

 npm i react-test-renderer --save-dev

……對於 Yarn,這個:

 yarn add react-test-renderer --dev

我們在package.json文件中還有一些配置要做。 根據 Expo React Native 的文檔,我們需要添加一個transformIgnorePattern配置,以防止在源文件與測試匹配時在 Jest 中運行測試(即,如果進行了測試並且在項目的node modules中找到了類似的文件)。

 "jest": { "preset": "jest-expo", "transformIgnorePatterns": [ "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)" ] }

現在,讓我們創建一個名為App.test.js的新文件來編寫我們的第一個測試。 我們將測試我們的App在其樹中是否有一個子元素:

 import React from "react"; import renderer from "react-test-renderer"; import App from "./App.js" describe("<App />", () => { it('has 1 child', () => { const tree = renderer.create(<App />).toJSON(); expect(tree.children.length).toBe(1); }); });

現在,運行yarn test或其 npm 等效項。 如果App.js有一個子元素,我們的測試應該通過,這將在命令行界面中得到確認。

在上面的代碼中,我們導入了Reactreact-test-renderer ,它們會渲染我們對Expo的測試。 我們已經將<App />組件樹轉換為 JSON,然後讓 Jest 查看 JSON 中返回的子組件數量是否等於我們的預期。

更多快照測試

正如 David Adeneye 所說:

“快照測試確保 Web 應用程序的用戶界面 (UI) 不會意外更改。 它在某個時刻捕獲組件的代碼,以便我們可以將處於一種狀態的組件與它可能採用的任何其他可能狀態進行比較。”

尤其是當項目涉及在許多組件中使用的全局樣式時,會這樣做。 讓我們為App.js寫一個快照測試來測試它的 UI 一致性:

 it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); }); it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });

將其添加到我們已經編寫的測試中,然後運行yarn test (或其 npm 等效項)。 如果我們的測試通過,我們應該看到:

 PASS src/App.test.js √ has 1 child (16ms) √ renders correctly (16ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 1 total Time: 24s

這告訴我們我們的測試通過了以及他們花費的時間。 如果測試通過,您的結果將看起來相似。

讓我們繼續在 Jest 中模擬一些函數。

模擬 API 調用

根據 Jest 的文檔:

模擬函數允許您通過擦除函數的實際實現、捕獲對函數的調用(以及在這些調用中傳遞的參數)、在使用 `new` 實例化時捕獲構造函數的實例以及允許測試來測試代碼之間的鏈接。返回值的時間配置。

簡單地說,mock 是一個對像或函數的副本,沒有該函數的實際工作。 它模仿了那個功能。

Mocks 以多種方式幫助我們測試應用程序,但主要好處是它們減少了我們對依賴項的需求。

模擬通常可以通過以下兩種方式之一進行。 一種是創建一個模擬函數,注入到要測試的代碼中。 另一種是編寫一個模擬函數來覆蓋附加到組件的包或依賴項。

大多數組織和開發人員更喜歡編寫模仿功能並使用假數據測試某些組件的手動模擬。

React Native 在全局對像中包含fetch 。 為了避免在我們的單元測試中進行真正的 API 調用,我們模擬了它們。 下面是一種在 React Native 中模擬所有(如果不是大多數)API 調用的方法,並且不需要依賴項:

 global.fetch = jest.fn(); // mocking an API success response once fetch.mockResponseIsSuccess = (body) => { fetch.mockImplementationForOnce ( () => Promise.resolve({json: () => Promise.resolve(JSON.parse(body))}) ); }; // mocking an API failure response for once fetch.mockResponseIsFailure = (error) => { fetch.mockImplementationForOnce( () => Promise.reject(error) ); };

在這裡,我們編寫了一個嘗試獲取 API 一次的函數。 完成此操作後,它返回一個 Promise,當它被解析時,它以 JSON 格式返回正文。 它類似於失敗的獲取事務的模擬響應——它返回一個錯誤。

下面是我們應用程序的product組件,包含一個product對象並將信息作為props返回。

 import React from 'react'; const Product = () => { const product = { name: 'Pizza', quantity: 5, price: '$50' } return ( <> <h1>Name: {product.name}</h1> <h1>Quantity: {product.quantity}</h1> <h1>Price: {product.price}</h1> </> ); } export default Product;

假設我們正在嘗試測試我們產品的所有組件。 直接訪問我們的數據庫不是一個可行的解決方案。 這就是模擬發揮作用的地方。 在下面的代碼中,我們試圖通過使用 Jest 描述組件中的對象來模擬產品的組件。

 describe("", () => { it("accepts products props", () => { const wrapper = mount(<Customer product={product} />); expect(wrapper.props().product).toEqual(product); }); it("contains products quantity", () => { expect(value).toBe(3); }); });

我們使用 Jest 中的describe來指示我們想要完成的測試。 在第一個測試中,我們正在檢查我們傳遞的對像是否等於我們模擬的道具。

在第二個測試中,我們通過customer道具來確保它是一個產品並且它與我們的模型相匹配。 這樣做,我們不必測試我們產品的所有組件,而且我們還可以防止代碼中的錯誤。

模擬外部 API 請求

到目前為止,我們一直在使用應用程序中的其他元素對 API 調用進行測試。 現在讓我們模擬一個外部 API 調用。 我們將使用 Axios。 要測試對 API 的外部調用,我們必須模擬我們的請求並管理我們得到的響應。 我們將使用axios-mock-adapter來模擬 Axios。 首先,我們需要通過運行以下命令來安裝axios-mock-adapter

 yarn add axios-mock-adapter

接下來要做的是創建我們的模擬:

 import MockAdapter from 'axios-mock-adapter'; import Faker from 'faker' import ApiClient from '../constants/api-client'; import userDetails from 'jest/mockResponseObjects/user-objects'; let mockApi = new MockAdapter(ApiClient.getAxiosInstance()); let validAuthentication = { name: Faker.internet.email(), password: Faker.internet.password() mockApi.onPost('requests').reply(config) => { if (config.data === validAuthentication) { return [200, userDetails]; } return [400, 'Incorrect username and password']; });

在這裡,我們調用ApiClient並將 Axios 實例傳遞給它以模擬用戶的憑據。 我們正在使用一個名為 faker.js 的包來生成虛假的用戶數據,例如電子郵件地址和密碼。

模擬的行為與我們期望的 API 一樣。 如果請求成功,我們將收到一個狀態碼為 200 的響應,表示 OK。 我們會得到一個狀態碼 400 來表示對服務器的錯誤請求,這將與 JSON 一起發送,並帶有消息“用戶名和密碼不正確”。

現在我們的 mock 已經準備好了,讓我們為外部 API 請求編寫一個測試。 和以前一樣,我們將使用快照。

 it('successful sign in with correct credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(getActions()).toMatchSnapshot(); }); it('unsuccessful sign in with wrong credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'wrong credential')) .catch((error) => { expect(errorObject).toMatchSnapshot(); });

在這裡,我們使用本機 JavaScript async await來保存我們的輸入,以測試是否使用正確的憑據成功登錄。 同時,來自 Jest 的authenticateUser函數對請求進行身份驗證,並確保它與我們之前的快照相匹配。 接下來,我們會測試登錄不成功的憑據(例如電子郵件地址或密碼)是否存在錯誤,並發送錯誤作為響應。

現在,運行yarn testnpm test 。 我相信你所有的測試都會通過。

讓我們看看如何在狀態管理庫 Redux 中測試組件。

使用快照測試 Redux Actions 和 Reducers

不可否認,Redux 是 React 應用程序中使用最廣泛的狀態管理器之一。 Redux 中的大部分功能都涉及dispatch ,它是 Redux 存儲的一個功能,用於觸發應用程序狀態的更改。 測試 Redux 可能會很棘手,因為 Redux 的actions在規模和復雜性上迅速增長。 使用 Jest 快照,這變得更容易。 大多數使用 Redux 的測試歸結為兩件事:

  • 為了測試actions ,我們創建了redux-mock-store並分發動作。
  • 為了測試 reducer,我們導入reducer並向其傳遞狀態和操作對象。

下面是一個帶有快照的 Redux 測試。 我們將通過在SIGN-IN驗證用戶並查看user reducer 如何處理LOGOUT操作來測試調度的操作。

 import mockStore from 'redux-mock-store'; import { LOGOUT } from '../actions/logout'; import User from '../reducers/user'; import { testUser } from 'jest/mock-objects'; describe('Testing the sign in authentication', () => { const store = mockStore(); it('user attempts with correct password and succeeds', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(store.getActions()).toMatchSnapshot(); }); }); describe('Testing reducers after user LOGS OUT', () => { it('user is returned back to initial app state', () => { expect(user(testUser, { type: LOGOUT })).toMatchSnapshot(); }); });

在第一個測試中,我們描述了登錄身份驗證並創建了一個模擬商店。 我們首先從 Redux 導入一個mockStore ,然後從 Jest 導入一個名為testUser的方法來幫助我們模擬用戶。 接下來,我們測試用戶何時使用與快照存儲中的電子郵件地址和密碼匹配的電子郵件地址和密碼成功登錄應用程序。 因此,快照確保每次運行測試時用戶輸入的對像都匹配。

在第二個測試中,我們正在測試用戶何時註銷。 一旦我們的 reducer 快照確認用戶已註銷,它就會返回到應用程序的初始狀態。

接下來,我們通過運行yarn test 。 如果測試通過,我們應該看到以下結果:

 PASS src/redux/actions.test.js √ user attempts with correct password and succeeds (23ms) √ user is returned back to initial app state (19ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 2 total Time: 31s

結論

使用 Jest,測試 React Native 應用程序從未如此簡單,尤其是使用快照,它確保 UI 保持一致,而不管全局樣式如何。 此外,Jest 允許我們在應用程序中模擬某些 API 調用和模塊。 我們可以通過測試 React Native 應用程序的組件來更進一步。

更多資源

  • “使用 Jest 測試 React Native 應用程序的實用指南”,David Adeneye,Smashing Magazine
  • 笑話文檔
  • “使用 Jest 進行測試”,Expo React Native 文檔
  • “學習用 Jest 測試 React Native”,Jason Gaare