測試你的 React App
tags: React
Testing
category: Front-End
description: 測試你的 React App
created_at: 2022/01/15 13:00:00
前言
這裡假設用最基本的 Counter
當作測試用的範例
事前準備
- 裝好
Node.js
先建立好 React 專案
$ npx create-react-app my-app
基本指令
test(測試描述[string], 測試主體[function])
render(元件[JSX])
screen 相關函數
expect 相關函數
fireEvent 相關函數
先看看 React 預設的 test file
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();
});
如果你去跑測試
$ npm test
or
$ npm run test
or yarn
yarn test
會看到類似這樣的東西
PASS src/App.test.js
√ renders learn react link (29 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.275 s
Ran all test suites.
Watch Usage
› Press f to run only failed tests.
› Press o to only run tests related to changed files.
› Press q to quit watch mode.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press Enter to trigger a test run.
可以發現說其實 renders learn react link
就是 test 函數的第一個參數,那下面來說明測試主體做了哪些事
render(<App />); // 渲染 <App /> 組件
const linkElement = screen.getByText(/learn react/i); // 從全域的 screen 變數去取得 element , 這邊使用的是 ByText 就是從文字去抓,可以支援 Regex
expect(linkElement).toBeInTheDocument(); // 預期 linkElement 有存在
如果你把它的 Text 亂改一下,例如:
const linkElement = screen.getByText(/learn react!!/i);
再跑測試就會看到一大串錯誤,像是下面這樣
FAIL src/App.test.js
× renders learn react link (36 ms)
● renders learn react link
TestingLibraryElementError: Unable to find an element with the text: /learn react!!/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
Ignored nodes: comments, <script />, <style />
<body>
<div>
<div
class="App"
>
<header
class="App-header"
>
<img
alt="logo"
class="App-logo"
src="logo.svg"
/>
<p>
Edit
<code>
src/App.js
</code>
and save to reload.
</p>
<a
class="App-link"
href="https://reactjs.org"
rel="noopener noreferrer"
target="_blank"
>
Learn React
</a>
</header>
</div>
</div>
</body>
4 | test('renders learn react link', () => {
5 | render(<App />);
> 6 | const linkElement = screen.getByText(/learn react!!/i);
| ^
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:38:19)
at node_modules/@testing-library/dom/dist/query-helpers.js:90:38
at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
at getByText (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
at Object.<anonymous> (src/App.test.js:6:30)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 2.255 s
Ran all test suites.
Watch Usage
› Press f to run only failed tests.
› Press o to only run tests related to changed files.
› Press q to quit watch mode.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press Enter to trigger a test run.
再來就開始實作基本的範例吧
實作範例
這個範例很簡單,目的就是做一個容器顯示 count
, 然後有兩個按鈕,一個是 +
,一個是 -
,點對應的按鈕就會對 count
做 +-1
的操作。
因為範例很簡單,那這邊順便做一下 TDD(Test-driven development)
,先寫測試。
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import App from './App'
test('renders + button', () => {
render(<App />) // 先渲染 <App />
const buttonElement = screen.getByRole('button', { name: '+' }) // 抓取 role 為 button, 且內容為 + 的按鈕
expect(buttonElement).toBeInTheDocument() // 它存在畫面上
})
test('renders - button', () => {
render(<App />)
const buttonElement = screen.getByRole('button', { name: '-' })
expect(buttonElement).toBeInTheDocument()
})
test('renders count span', () => {
render(<App />)
const spanElement = screen.getByTestId('count-span') // 從 testid 去抓 element
expect(spanElement).toBeInTheDocument()
})
test('test + button function', () => {
render(<App />)
const buttonElement = screen.getByRole('button', { name: '+' })
const spanElement = screen.getByTestId('count-span')
fireEvent.click(buttonElement) // 對按鈕觸發點擊事件
expect(spanElement.textContent).toBe('1') // 點擊後 span 的文字應該要為1
})
test('test - button function', () => {
render(<App />)
const buttonElement = screen.getByRole('button', { name: '-' })
const spanElement = screen.getByTestId('count-span')
fireEvent.click(buttonElement)
expect(spanElement.textContent).toBe('-1')
})
這樣直接去跑測試
FAIL src/App.test.js
× renders + button (142 ms)
× renders - button (34 ms)
× renders count span (4 ms)
× + button function (46 ms)
× - button function (29 ms)
全部都噴掉很正常,畢竟你根本還沒開始寫,自然不會通過測試,那再來只要寫好讓測試都通過就完成了。
再來我們先把畫面做好,在 App.js
填入下面程式
import React from 'react'
function App() {
return (
<div className="App">
<span data-testid="count-span">0</span>
<button>+</button>
<button>-</button>
</div>
)
}
export default App
再去跑一次測試
FAIL src/App.test.js
√ renders + button (70 ms)
√ renders - button (16 ms)
√ renders count span (4 ms)
× + button function (16 ms)
× - button function (12 ms)
會看到還是有錯誤,但是上面三個關於 render
的已經通過了,再來就缺少按鈕的功能
import React, { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
return (
<div className="App">
<span data-testid="count-span">{count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
)
}
export default App
再跑一次測試,會發現全部通過了
PASS src/App.test.js
√ renders + button (121 ms)
√ renders - button (19 ms)
√ renders count span (5 ms)
√ + button function (20 ms)
√ - button function (14 ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 2.697 s
Ran all test suites.
這時候去打開測試的 Server
會發現功能也正常。
最後更新時間: 2022年01月15日.