Nightwatch 中的組件測試已在 2.4 版本中進行了優化,並且對測試 React 組件的支援(通過 @nightwatch/react
外掛程式)得到了顯著改進。我們還發布了一個新的外掛程式,用於將流行的 Testing Library 與 Nightwatch 一起使用 – @nightwatch/testing-library
,自 Nightwatch v2.6 起可用。
現在,我們將建立一個詳細的範例,說明如何使用 Nightwatch 和 Testing Library 來測試 React 組件。我們將使用 複雜範例,該範例可在 React Testing Library 文件中找到,並使用 Jest 編寫。
本教學將涵蓋如何
- 使用 Vite 設置一個新的 React 專案,這也是 Nightwatch 內部用於組件測試的工具;
- 安裝和配置 Nightwatch 和 Testing Library;
- 使用
@nightwatch/api-testing
外掛程式模擬 API 請求; - 使用 Nightwatch 和 Testing Library 編寫複雜的 React 組件測試。
步驟 0. 建立一個新專案
首先,我們將使用 Vite 建立一個新專案
npm init vite@latest
當出現提示時,選擇 React
和 JavaScript
。這將使用 React 和 JavaScript 建立一個新專案。
步驟 1. 安裝 Nightwatch 和 Testing Library
React 的 Testing Library 可以使用 @testing-library/react
套件安裝
npm i @testing-library/react --save-dev
若要安裝 Nightwatch,請執行 init 命令
npm init nightwatch@latest
當出現提示時,選擇 Component testing
和 React
。這將安裝 nightwatch
和 @nightwatch/react
外掛程式。選擇一個瀏覽器來安裝驅動程式。在此範例中,我們將使用 Chrome。
1.1. 安裝 @nightwatch/testing-library 外掛程式
自 v2.6 起,Nightwatch 提供了自己的外掛程式,可直接將 Testing Library 查詢作為命令使用。我們稍後需要它來編寫我們的測試,所以我們現在就安裝它
npm i @nightwatch/testing-library --save-dev
1.2 安裝 @nightwatch/apitesting 外掛程式
此範例包含一個用於測試組件的模擬伺服器。我們將使用 @nightwatch/apitesting
外掛程式隨附的整合式模擬伺服器。使用以下命令安裝它
npm i @nightwatch/apitesting --save-dev
步驟 2. 建立 Login 組件
我們將使用與 React Testing Library 文件中相同的組件。建立一個新檔案 src/Login.jsx
並新增以下程式碼
// login.jsx
import * as React from 'react'
function Login() {
const [state, setState] = React.useReducer((s, a) => ({...s, ...a}), {
resolved: false,
loading: false,
error: null,
})
function handleSubmit(event) {
event.preventDefault()
const {usernameInput, passwordInput} = event.target.elements
setState({loading: true, resolved: false, error: null})
window
.fetch('http://localhost:3000/api/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: usernameInput.value,
password: passwordInput.value,
}),
})
.then(r => r.json().then(data => (r.ok ? data : Promise.reject(data))))
.then(
user => {
setState({loading: false, resolved: true, error: null})
window.localStorage.setItem('token', user.token)
},
error => {
setState({loading: false, resolved: false, error: error.message})
},
)
}
return (
<div>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="usernameInput">Username</label>
<input id="usernameInput" />
</div>
<div>
<label htmlFor="passwordInput">Password</label>
<input id="passwordInput" type="password" />
</div>
<button type="submit">Submit{state.loading ? '...' : null}</button>
</form>
{state.error ? <div role="alert">{state.error}</div> : null}
{state.resolved ? (
<div role="alert">Congrats! You're signed in!</div>
) : null}
</div>
)
}
export default Login
步驟 3. 建立組件測試
Testing Library 的基本原則之一是,測試應盡可能地模擬使用者與應用程式互動的方式。當在 Nightwatch 中使用 JSX 編寫組件測試時,我們需要使用 Component Story Format(由 Storybook 引入的宣告式格式)將測試編寫為組件故事。
這使我們能夠編寫專注於組件使用方式而非實作方式的測試,這符合 Testing Library 的理念。您可以在 Nightwatch 文件中閱讀更多相關資訊。
使用此格式編寫測試的好處是,我們可以重複使用相同的程式碼來編寫組件的故事,這些故事可用於記錄和展示 Storybook 中的組件。
3.1 使用有效憑證登入測試
建立一個新檔案 src/Login.spec.jsx
並新增以下程式碼,其功能與使用 Jest 編寫的 複雜範例 相同
若要在 Nightwatch 中使用 JSX 呈現組件,我們只需為呈現的組件建立一個匯出,並可選擇性地設定一組 props。play
和 test
函數用於與組件互動並驗證結果。
play
用於與組件互動。它在瀏覽器內容中執行,因此我們可以從 Testing Library 使用screen
物件來查詢 DOM 並觸發事件;test
用於驗證結果。它在 Node.js 內容中執行,因此我們可以使用 Nightwatchbrowser
物件來查詢 DOM 並驗證結果。
// login.spec.jsx
import {render, fireEvent, screen} from '@testing-library/react'
import Login from '../src/login'
export default {
title: 'Login',
component: Login
}
export const LoginWithValidCredentials = () => <Login />;
LoginWithValidCredentials.play = async ({canvasElement}) => {
//fill out the form
};
LoginWithValidCredentials.test = async (browser) => {
// verify the results
};
新增模擬伺服器
此範例使用模擬伺服器來模擬登入請求。我們將使用 @nightwatch/apitesting
外掛程式隨附的整合式模擬伺服器。
為此,我們將使用 setup
和 teardown
hooks,我們可以將它們直接編寫在測試檔案中。這兩個 hooks 都在 Node.js 內容中執行。
我們還需要在 Login
組件中將登入端點設定為 http://localhost:3000/api/login
,這是模擬伺服器的 URL。
完整的測試檔案
完整的測試檔案如下所示
// login.spec.jsx
import {render, fireEvent, screen} from '@testing-library/react'
import Login from '../src/Login'
let server;
const token = 'fake_user_token';
let serverResponse = {
status: 200,
body: {token}
};
export default {
title: 'Login',
component: Login,
setup: async ({mockserver}) => {
server = await mockserver.create();
server.setup((app) => {
app.post('/api/login', function (req, res) {
res.status(serverResponse.status).json(serverResponse.body);
});
});
await server.start(mockServerPort);
},
teardown: async (browser) => {
await browser.execute(function() {
window.localStorage.removeItem('token')
});
await server.close();
}
}
export const LoginWithValidCredentials = () => <Login />;
LoginWithValidCredentials.play = async ({canvasElement}) => {
//fill out the form
fireEvent.change(screen.getByLabelText(/username/i), {
target: {value: 'chuck'},
});
fireEvent.change(screen.getByLabelText(/password/i), {
target: {value: 'norris'},
});
fireEvent.click(screen.getByText(/submit/i))
};
LoginWithValidCredentials.test = async (browser) => {
const alert = await browser.getByRole('alert')
await expect(alert).text.to.match(/congrats/i)
const localStorage = await browser.execute(function() {
return window.localStorage.getItem('token');
});
await expect(localStorage).to.equal(fakeUserResponse.token)
};
偵錯
除了擁有與端對端測試相同的 API 外,使用 Nightwatch 進行組件測試的主要好處之一是,我們可以在真實瀏覽器中執行測試,而不是在虛擬 DOM 環境(例如 JSDOM)中執行測試。
這使我們能夠使用 Chrome 開發人員工具來偵錯測試。
例如,我們可以在 LoginWithValidCredentials.play
函數中新增一個 debugger
陳述式
LoginWithValidCredentials.play = async ({canvasElement}) => {
//fill out the form
fireEvent.change(screen.getByLabelText(/username/i), {
target: {value: 'chuck'},
});
fireEvent.change(screen.getByLabelText(/password/i), {
target: {value: 'norris'},
});
debugger;
fireEvent.click(screen.getByText(/submit/i))
};
現在,我們使用 --debug
和 --devtools
旗標執行測試
npx nightwatch test/login.spec.jsx --debug --devtools
這將開啟一個新的 Chrome 視窗,其中開啟了開發人員工具。我們現在可以在開發人員工具中設定一個中斷點,並逐步執行程式碼。

3.2 使用伺服器例外狀況登入測試
Testing Library 文件中的原始 範例 也包含在伺服器擲回例外狀況時的測試案例。
讓我們嘗試在 Nightwatch 中編寫相同的程式碼。這次我們只會使用 test
函數,因為我們也可以使用這種方式與組件互動。正如我們前面提到的,test
函數會在 Node.js 內容中執行,並且它會接收 Nightwatch browser
物件作為引數。
我們還需要更新模擬伺服器回應,以傳回 500 狀態碼和錯誤訊息。我們可以通過在 LoginWithServerException
組件故事中編寫 preRender
測試 hook 來輕鬆達成此目的。
export const LoginWithServerException = () => <Login />;
LoginWithServerException.preRender = async (browser) => {
serverResponse = {
status: 500,
body: {message: 'Internal server error'}
};
};
LoginWithServerException.test = async (browser) => {
const username = await browser.getByLabelText(/username/i);
await username.sendKeys('chuck');
const password = await browser.getByLabelText(/password/i);
await password.sendKeys('norris');
const submit = await browser.getByText(/submit/i);
await submit.click();
const alert = await browser.getByRole('alert');
await expect(alert).text.to.match(/internal server error/i);
const localStorage = await browser.execute(function() {
return window.localStorage.getItem('token');
});
await expect(localStorage).to.equal(token)
};
4. 執行測試
最後,我們來執行測試。這將在 Chrome 中執行 LoginWithValidCredentials
和 LoginWithServerException
組件故事。
npx nightwatch test/login.spec.jsx
若要在不開啟瀏覽器的情況下執行測試,我們可以傳遞 --headless
旗標。
如果一切順利,您應該會看到以下輸出
[Login] Test Suite
────────────────────────────────────
ℹ Connected to ChromeDriver on port 9515 (1134ms).
Using: chrome (108.0.5359.124) on MAC OS X.
Mock server listening on port 3000
Running <LoginWithValidCredentials> component:
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[browser] [vite] connecting...
[browser] [vite] connected.
✔ Expected element <LoginWithValidCredentials> to be visible (15ms)
✔ Expected element <DIV[id='app'] > DIV > DIV> text to match: "/congrats/i" (14ms)
✔ Expected 'fake_user_token' to equal('fake_user_token'):
✨ PASSED. 3 assertions. (1.495s)
Running <LoginWithServerException> component:
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[browser] [vite] connecting...
[browser] [vite] connected.
✔ Expected element <LoginWithServerException> to be visible (8ms)
✔ Expected element <DIV[id='app'] > DIV > DIV> text to match: "/internal server error/i" (8ms)
✔ Expected 'fake_user_token' to equal('fake_user_token'):
✨ PASSED. 3 assertions. (1.267s)
✨ PASSED. 6 total assertions (4.673s)
5. 結論
就是這樣!您可以在 GitHub 存放庫中找到此範例的完整程式碼。歡迎提交 PR。
如果您有任何問題或意見反應,請隨時加入 Nightwatch Discord。