Nightwatchでのコンポーネントテストはバージョン2.4で改良され、Reactコンポーネントのテスト(@nightwatch/react
プラグイン経由)のサポートが大幅に改善されました。また、人気のTesting LibraryをNightwatchで使用するための新しいプラグイン – @nightwatch/testing-library
もリリースしました。これはNightwatch v2.6以降で利用可能です。
ここでは、NightwatchとTesting Libraryを使用してReactコンポーネントをテストする方法の詳細な例を作成します。私たちは、Jestで書かれているReact Testing Libraryのドキュメントにある複雑な例を使用します。
このチュートリアルでは、次の方法について説明します。
- 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の基本原則の1つは、テストはユーザーがアプリケーションを操作する方法にできる限り似ている必要があるということです。JSXを使用してNightwatchでコンポーネントテストを作成する場合、Component Story Formatを使用して、コンポーネントストーリーとしてテストを記述する必要があります。これは、Storybookによって導入された宣言的な形式です。
これにより、実装方法ではなく、コンポーネントの使用方法に焦点を当てたテストを作成できます。これは、Testing Libraryの理念に沿ったものです。詳細については、Nightwatchドキュメントを参照してください。
この形式を使用してテストを作成することの素晴らしい点は、同じコードを使用してコンポーネントのストーリーを作成できることです。これは、Storybookでコンポーネントをドキュメント化し、紹介するために使用できます。
3.1 有効な認証情報によるログインテスト
新しいファイルsrc/Login.spec.jsx
を作成し、Jestで書かれた複雑な例と同じことを行う次のコードを追加します。
NightwatchでJSXを使用してコンポーネントをレンダリングするには、レンダリングされたコンポーネントのエクスポートを作成するだけです。オプションで、一連のプロップスを使用できます。play
およびtest
関数は、コンポーネントと対話し、結果を検証するために使用されます。
play
はコンポーネントと対話するために使用されます。ブラウザコンテキストで実行されるため、Testing Libraryのscreen
オブジェクトを使用してDOMをクエリし、イベントを発生させることができます。test
は結果を検証するために使用されます。Node.jsコンテキストで実行されるため、Nightwatchのbrowser
オブジェクトを使用して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
フックを使用します。両方のフックは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をコンポーネントテストに使用する主な利点の1つは、JSDOMなどの仮想DOM環境ではなく、実際のブラウザでテストを実行できることです。
これにより、Chrome Dev Toolsを使用してテストをデバッグできます。
たとえば、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
これにより、Dev Toolsが開いた新しいChromeウィンドウが開きます。Dev Toolsにブレークポイントを設定して、コードをステップ実行できます。

3.2 サーバー例外でのログインテスト
Testing Libraryドキュメントの元の例には、サーバーが例外をスローした場合のテストも含まれています。
Nightwatchで同じものを記述してみましょう。今回はtest
関数のみを使用します。これは、この方法でもコンポーネントと対話できるためです。前述したように、test
関数はNode.jsコンテキストで実行され、Nightwatchのbrowser
オブジェクトを引数として受け取ります。
また、モックサーバーの応答を更新して、500ステータスコードとエラーメッセージを返す必要があります。これは、LoginWithServerException
コンポーネントストーリーにpreRender
テストフックを記述することで簡単に実現できます。
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までお気軽にお越しください。