
大家好,我是前端西瓜哥。
Jest 是一款輕量的 JavaScript 測試框架,它的賣點是簡單好用,由 facebook 出品。本文就簡單講講如何使用 Jest 對 React 組件進行測試。
為什么需要單元測試?
單元測試(Unit Testing),指的是對程序中的模塊(最小單位)進行檢查和驗證。比如一個函數、一個類、一個組件,它們都是模塊。
使用單元測試的優點:
- 更好地交付高質量代碼。代碼不可能沒有 bug,測試能幫你找出來;
- 更容易重構。我們不愿意去重構代碼,不去還技術債,很大原因是測試覆蓋率不足,害怕遺漏一些邊邊角角的邏輯,導致線上發生重大事故;
- 可以用測試描述模塊功能。注釋和文檔容易忘記修改,但測試用例的描述永遠是準確的,因為不對就無法通過測試;
- 可測試性好的代碼,往往可維護性更好。比如某個模塊很難測試,是因為它和其他模塊高度耦合,此時你需要替換為依賴注入的方式來管理模塊依賴。
Jest 判定測試腳本
Jest 需要 確認哪些是測試文件,默認判斷測試文件的邏輯是:
- __tests__? 文件夾下的 .js .jsx、.ts 、.tsx 為后綴的文件。
- test.js? 、spec.js 或其他文件后綴 .jsx、.ts 、.tsx。
可以通過設置 Jest 配置文件的 testMatch 或 testRegex 選項進行修改,或者 package.json 下的 "jest" 屬性。
Jest 基本使用
我們先寫一個簡單的函數,作為被測試的模塊。
function sum(a, b) {
return a + b;
}
export default sum;
然后我們用 Jest 來做測試。
import sum from './sum';
test('1 + 1 應該等于 2', () => {
expect(sum(1, 1)).toBe(2);
});
然后執行 jest 命令,得到測試結果。

test 方法創建了一個測試的作用域,該方法有三個參數:
- 測試的描述。
- 我們寫測試代碼的函數。
- 測試超時時間,默認為 5 秒,有些測試是異步的,我們需要等待。
test 方法有一個別名叫做 it,二者的功能是一致的,只是語義不同。通常用 test,但在某些情況下更適合用 it。這種情況就是 it 可以和描述語句拼成一句話的時候,比如:
it('should be true', () => { /* 測試內容 */});
it 方法和后面的 should be true 拼成了一句主語為 it 的句子,語義更好。
我們通常使用 expect 來測試一個模塊的邏輯是否符合預期。expect 會將模塊返回的結果封裝成一個對象,然后提供非常豐富的方法做測試。
比如 toBe 就可以做 Object.is 的對比測試。
// sum(1, 1) 的結果是否為 2
expect(sum(1, 1)).toBe(2);
expect 的實現思路大致為:
function expect(value) {
return {
toBe(comparedValue) {
if (Object.is(value, comparedValue)) {
// 記錄測試成功
} else {
// 記錄測試失敗
}
},
// 其他 API
toBeTruthy() { /* ... */ },
// ...
}
}
利用了閉包。
還有一些其他的 toXX API,我們稱為 matcher。比如:
- toEqual:對對象進行深遞歸的 Object.is 對比。
- toBeTruthy:是否為真值。
- not:對結果取反,比如expect(val).not.beBe(otherVal) 表示兩值不相等才通過測試。
- toContain:數組中是否含有某個元素。
- toBeLessThan:是否小于某個值,可以做性能測試,執行某個函數幾千次,時間不能高于某個值。
更多 API 可以看文檔:
??https://jestjs.io/docs/expect。??
你可以用 describe 方法將多個相關的 test 組合起來,這樣能讓你的測試用例更好地被組織,測試報告輸出也更有條理。
describe('一個有多個屬性的對象的測試', () => {
test('test 1', async () => {
expect(obj.a).toBeTruthy();
});
test('test 2', async () => {
expect(obj.b).toBeTruthy();
});
});
describe 里面可以嵌套 describe,即組里面還可以有組。
異步測試
如果使用異步測試,需要將 Promise 作為返回值。
test('請求測試', () => {
return getData().then(res {
expect(res.data.success).toBe(true);
})
})
或使用 async / await。
test('請求測試', async () => {
const res = await getData();
expect(res.data.success).toBe(true);
})
也支持回調函數風格的測試,你需要調用函數傳入的 done 函數來表明測試完成:
test('異步測試', done => {
setTimeout(() {
expect('前端西瓜哥').toBeTruthy();
done();
}, 2000);
});
生命周期函數
beforeAll,在當前文件的正式開始測試前執行一次,適合做一些每次 test 前都要做的初始化操作,比如數據庫的清空以及初始化。
beforeEach,在當前文件的每個 test 執行前都調用一次。
afterAll,在當前文件所有測試結束后執行一次,適合做一些收尾工作,比如將數據庫清空。
afterEach,在當前文件的每個 test 執行完后都調用一次。
React Testing Library
本文不講解安裝和配置,我們先用 CreateReactApp 來搭建項目,并使用 TypeScript 模板。
yarn create react-app jest-app --template typescript
執行單元測試的命令為:
CreateReactApp 內置了 Jest,但 Jest 本身并不支持 React 組件的測試 API,需要使用另外一個內置的 React Testing Library 庫來測試 React 組件。
React Testing Library 是 以用戶為角度 的測試庫,能夠模擬瀏覽器的 DOM,將 React 組件掛載上去后,我們使用其提供的一些模擬用戶操作的 API 進行測試。
React Testing Library 的哲學是:
測試的寫法越是接近應用被使用的方式,我們就越有自信將其交付給客戶。
CreateReactApp 預置模板的 App.test.tsx 使用了 React Testing Library。
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
Enzyme
另一種比較流行的測試 React 組件的框架是 Enzyme,它的 API 簡潔優雅,能夠用類似 JQuery 的語法,對開發非常友好。Enzyme 由 Airbnd 出品,但目前已經不怎么維護了。
為此,你需要裝一些包:
yarn add -D enzyme enzyme-adapter-react-16
如果你使用了 TS,你還得補上類型聲明。
yarn add -D @types/enzyme @types/enzyme-adapter-react-16
示例:
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Button from '../button';
Enzyme.configure({ adapter: new Adapter() });
it('Button with children', () => {
const text = 'confirm';
const btn = shallow(<Button>{text}</Button>);
expect(btn.text()).toBe(text);
});
目前(2022.10.25) enzyme 官方只支持到 React 16,Enzyme 已死:
??https://dev.to/wojtekmaj/enzyme-is-dead-now-what-ekl。??
使用 Jest 測試 React 組件
我們先實現一個簡單的 Button 組件。
import { CSSProperties, MouseEvent, FC } from 'react';
import classNames from 'classnames';
import './style.scss';
const clsPrefix = 'xigua-ui-btn';
export type ButtonProps = {
type?: 'primary' | 'default'
size?: 'large' | 'middle' | 'small';
disabled?: boolean;
children?: React.ReactNode;
onClick?: (event: MouseEvent) => void;
style?: CSSProperties;
className?: string;
}
const Button: FC<ButtonProps> = (props) => {
const {
type = 'default',
size = 'middle',
disabled = false,
children,
onClick,
style,
className,
} = props;
const mixedClassName = classNames(
clsPrefix,
`${clsPrefix}-${type}`,
`${clsPrefix}-${size}`,
className
);
return (
<button
style={style}
className={mixedClassName}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
};
export default Button;
然后我們創建一個 button.test.tsx 測試文件。我們使用 React Testing Library。
我們寫個測試。
import { render, screen } from '@testing-library/react';
import Button from '../button';
test('Button with children', () => {
const text = 'confirm Btn';
render(<Button>{text}</Button>);
screen.debug();
});
render 方法會將 React 組件掛載到虛擬的文檔樹上。screen.debug() 用于調試,能讓我們看到虛擬樹的完整結構。
<body>
<div>
<button
class="xigua-ui-btn xigua-ui-btn-default xigua-ui-btn-middle"
>
confirm Btn
</button>
</div>
</body>
測試 Button 的文本內容是否正常顯示:
test('Button with children', () => {
const text = 'confirm Btn';
// 渲染 Button 組件
render(<Button>{text}</Button>);
// 找到內容為 text 的元素
const BtnElement = screen.getByText(text);
// 測試元素是否在 Document 上
expect(BtnElement).toBeInTheDocument();
});
測試 Button 的 onClick 能否正常觸發:
test('Button click', () => {
let toggle = false;
render(<Button onClick={() => { toggle = true; }} />);
// 找到第一個 button 元素,然后觸發它的點擊事件
fireEvent.click(screen.getByRole('button'));
// 看看 toggle 變量是否變成 true
expect(toggle).toBe(true);
});
測試 Button 的 className 是否成功添加:
test('Button with custom className', () => {
const customCls = 'customBtn';
render(<button className={customCls} />);
// 找到按鈕元素
const btn = screen.getByRole('button');
// 元素的 className 列表上是否有我們傳入的 className
expect(btn).toHaveClass(customCls);
});
源碼:
??https://github.com/F-star/xigua-ui/blob/main/src/components/button/??tests/button.test.tsx。
執行 yarn test :

結尾
為了讓代碼更健壯,做模塊的單元測試還是有必要的,Jest 作為流行的測試庫值得一試。