Jest 和 React Testing Library (RTL) 是前端開發中用於測試 React 應用的首選工具。Jest 是一個功能豐富的JavaScript測試框架,而React Testing Library 是一種提倡以用户角度編寫測試的庫,它鼓勵測試組件的行為而不是內部實現細節。
安裝和配置
首先,確保你已經安裝了react, react-dom, jest, @testing-library/react, 和 @testing-library/jest-dom。在你的package.json中添加以下依賴:
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
# 或
yarn add --dev jest @testing-library/react @testing-library/jest-dom
在jest.config.js中配置Jest,例如:
module.exports = {
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
testEnvironment: 'jsdom',
};
基本測試結構
創建一個測試文件,通常與你的組件文件同名,但帶有.test.js或.test.tsx後綴。下面是一個簡單的組件測試示例:
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('renders correctly', () => {
render(<MyComponent />);
expect(screen.getByText('Hello, world!')).toBeInTheDocument();
});
it('handles button click', () => {
render(<MyComponent />);
const button = screen.getByRole('button', { name: /click me/i });
fireEvent.click(button);
expect(screen.getByText(/clicked/i)).toBeInTheDocument();
});
});
測試組件行為
使用render函數渲染組件,並使用screen對象來查詢DOM,確保組件按預期渲染。getByText, getByRole, getByPlaceholderText等輔助函數可以幫助找到元素。
模擬(Mocking)
Jest 提供了強大的模擬功能,可以模擬組件的依賴,例如API調用。例如,模擬一個fetch調用:
import fetch from 'jest-fetch-mock';
beforeAll(() => {
fetch.mockResponseOnce(JSON.stringify({ data: 'mocked response' }));
});
it('fetches data on mount', async () => {
render(<MyComponent />);
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
});
事件處理
使用fireEvent函數觸發組件上的事件,比如點擊按鈕或提交表單。
const button = screen.getByRole('button');
fireEvent.click(button);
清理和解構
在每個測試之後,確保清理掉任何副作用,如添加到DOM中的元素。afterEach鈎子可以用於此目的:
afterEach(() => {
cleanup();
});
異步測試
使用waitFor或async/await處理異步操作,確保組件在測試中達到期望狀態:
it('loads data after fetching', async () => {
render(<MyComponent />);
await waitFor(() => expect(screen.getByText('Data loaded')).toBeInTheDocument());
});
測試狀態和副作用
使用jest.useFakeTimers()和act函數來測試狀態變化和副作用,如定時器或副作用函數:
jest.useFakeTimers();
it('displays loading state', () => {
render(<MyComponent />);
act(() => jest.advanceTimersByTime(1000));
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
組件庫的測試
對於複雜的組件庫,可以創建一個setupTests.js文件來設置全局的模擬和配置,例如:
import '@testing-library/jest-dom';
import fetchMock from 'jest-fetch-mock';
fetchMock.enableMocks(); // 如果使用fetch模擬
性能優化
使用jest-environment-jsdom-sixteen或jest-environment-jsdom-thirteen可以減少測試的內存消耗。
測試組件的交互性
React Testing Library 強調測試組件的行為,而不是它的實現細節。以下是一些測試組件交互性的最佳實踐:
測試用户交互
使用fireEvent模擬用户行為,例如點擊、輸入和選擇:
const input = screen.getByLabelText('Search');
fireEvent.change(input, { target: { value: 'search term' } });
expect(input).toHaveValue('search term');
確保組件響應變化
測試組件如何響應狀態或props的變化:
const toggleButton = screen.getByRole('button', { name: 'Toggle' });
fireEvent.click(toggleButton);
expect(screen.getByTestId('visible-element')).toBeInTheDocument();
驗證數據渲染
測試組件是否正確呈現從API獲取的數據:
const data = { title: 'Example Title' };
fetchMock.mockResponseOnce(JSON.stringify(data));
render(<MyComponent />);
await waitFor(() => expect(screen.getByText('Example Title')).toBeInTheDocument());
錯誤和異常處理
測試組件在錯誤發生時的行為,例如驗證錯誤消息的顯示:
it('displays error message when fetching fails', async () => {
fetchMock.mockRejectOnce(new Error('Network error'));
render(<MyComponent />);
await waitFor(() => expect(screen.getByText('Error: Network error')).toBeInTheDocument());
});
清晰的測試描述
編寫有意義的測試描述,讓測試結果易於理解:
it('renders search results when query is submitted', async () => {
// ...
});
測試組件的邊緣情況
確保覆蓋組件的所有邊緣情況,包括空值、異常數據和邊界條件:
it('displays loading state when data is fetching', () => {
render(<MyComponent isLoading />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('displays empty state when no data is found', () => {
render(<MyComponent data={[]} />);
expect(screen.getByText('No results found.')).toBeInTheDocument();
});
代碼覆蓋率報告
使用jest-coverage插件生成代碼覆蓋率報告,確保有足夠的測試覆蓋:
npx jest --coverage
持續集成
將測試集成到持續集成(CI)流程中,確保代碼質量始終如一:
# .github/workflows/test.yml (GitHub Actions)
name: Test
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
高級測試技巧
Mocking和Spying
Jest 提供了模擬(mocking)和監聽(spying)功能,用於控制和檢查函數行為:
import myFunction from './myFunction';
jest.spyOn(myModule, 'myFunction');
// 在測試中調用函數
myFunction();
// 檢查函數是否被調用
expect(myFunction).toHaveBeenCalled();
// 檢查函數調用的具體參數
expect(myFunction).toHaveBeenCalledWith(expectedArgs);
// 重置模擬
myFunction.mockReset();
// 重置並清除模擬的返回值和調用記錄
myFunction.mockClear();
// 恢復原函數
myFunction.mockRestore();
測試異步邏輯
使用async/await和await waitFor處理異步操作:
it('fetches data and updates state', async () => {
// 模擬API返回
fetchMock.mockResolvedValueOnce({ json: () => Promise.resolve({ data: 'mocked data' }) });
render(<MyComponent />);
// 等待數據加載完成
await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
// 驗證狀態更新
expect(screen.getByText('mocked data')).toBeInTheDocument();
});
測試生命週期方法
使用act包裹組件的生命週期方法,確保它們在測試環境中正確執行:
import { act } from 'react-dom/test-utils';
it('calls componentDidMount', () => {
const mockFn = jest.fn();
const MyComponent = () => {
useEffect(mockFn);
return <div>Component</div>;
};
act(() => {
render(<MyComponent />);
});
expect(mockFn).toHaveBeenCalled();
});
使用createRef和forwardRef
測試使用createRef或forwardRef的組件時,可以創建一個ref並傳遞給組件:
it('sets focus on the input element', () => {
const inputRef = React.createRef();
render(<MyComponent inputRef={inputRef} />);
act(() => {
inputRef.current.focus();
});
expect(document.activeElement).toBe(inputRef.current);
});
測試事件處理器
使用fireEvent模擬事件,但要確保在act中進行:
it('calls onChange handler', () => {
const onChangeHandler = jest.fn();
render(<MyComponent onChange={onChangeHandler} />);
const input = screen.getByRole('textbox');
act(() => {
fireEvent.change(input, { target: { value: 'new value' } });
});
expect(onChangeHandler).toHaveBeenCalledWith('new value');
});
性能優化
快速測試
減少渲染深度:只渲染必要的組件層級,避免渲染整個組件樹。
使用jest.spyOn代替jest.fn:對於性能敏感的函數,使用jest.spyOn代替jest.fn,因為它更快。
選擇性運行測試
使用--findRelatedTests選項只運行與更改相關的測試,以加快測試速度:
npx jest --findRelatedTests
使用快照測試
對於不經常更改的組件,使用快照測試可以節省時間:
it('renders snapshot correctly', () => {
const { container } = render(<MyComponent />);
expect(container.firstChild).toMatchSnapshot();
});
代碼覆蓋率閾值
設置代碼覆蓋率閾值,確保測試覆蓋了足夠的代碼:
// jest.config.js
module.exports = {
coverageThreshold: {
global: {
statements: 80,
branches: 80,
functions: 80,
lines: 80,
},
},
};
2500G計算機入門到高級架構師開發資料超級大禮包免費送!