Взгляд на популярную Testing Library с Nightwatch — и многое другое
![Nightwatch + Testing Library Nightwatch + Testing Library](https://habrastorage.org/getpro/habr/upload_files/c2a/e1d/b92/c2ae1db92c2974d123ecf8cc6b2c59aa.png)
Мы создадим подробный пример проекта React с Vite, а затем воспользуемся Nightwatch и Testing Library для тестирования этих компонентов. Мы используем Complex example, доступный в документах React Testing Library, написанный с помощью Jest.
В этом уроке мы рассмотрим следующее:
Установим новый проект React с помощью Vite, который также используется внутри Nightwatch для компонентного тестирования
Установим и сконфигурируем Nightwatch и Testing Library
Сымитируем API запросы с помощью плагина
@nightwatch/api-testing
ННапишем комплексный React-компонентный тест, используя Nightwatch и Testing Library.
Шаг 0. Создание нового проекта
Для начала мы создадим новый проект с Vite:
npm init vite@latest
Выберите React
и JavaScript
при появлении запроса. Это создаст новый проект с React и JavaScript.
Шаг 1. Установка Nightwatch и Testing Library
Testing Library для React может быть установлена с помощью пакета @testing-library/react
:
npm i @testing-library/react --save-dev
Чтобы установить Nightwatch, выполните команду инициализации:
npm init nightwatch@latest
Выберите React
и JavaScript
при появлении запроса. С помощью этого будет установлен nightwatch
и плагин @nightwatch/react
. Выберите браузер для установки драйвера. В этом примере мы будем использовать Chrome.
1.1. Установка плагина @nightwatch/testing-library
Начиная с версии 2.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 docs. Создаем новый файл 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('/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. Создание компонента Test
Один из основополагающих принципов Testing Library заключается в том, что тесты должны максимально походить на то, как пользователи взаимодействуют с приложением. При написании компонентных тестов в Nightwatch с использованием JSX нам нужно написать тест как компонентную историю, используя формат Component Story Format, декларативный формат, представленный Storybook.
Это позволяет нам писать тесты, сосредотачиваясь на том, как компонент используется, а не на том, как он реализован, что соответствует философии Testing Library. Подробнее об этом можно прочитать в документации Nightwatch docs.
Самое замечательное в использовании этого формата для написания наших тестов заключается в том, что мы можем использовать один и тот же код для написания историй для наших компонентов, которые могут быть использованы для документирования и демонстрации их в Storybook.
3.1. Тест входа с валидными данными
Создаем новый файл src/Login.spec.jsx
и добавляем следующий код, который делает то же самое, что и комплексный пример, написанный с помощью Jest:
Чтобы визуализировать компонент с помощью JSX в Nightwatch, мы создаем некий экспорт для визуализированного компонента, при необходимости с набором реквизитов. Функции play
и test
используются для взаимодействия с компонентом и для верификации результатов.
play
используется для взаимодействия с компонентом. Он выполняется в контексте браузера, поэтому мы можем использовать объектscreen
из Testing Library для запроса 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
, которые мы можем написать прямо в тестовом файле. Оба хука выполняются в контексте Node.js.
Нам также необходимо установить конечную точку входа в http://localhost:3000/api/login
в компоненте 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)
};
Дебагинг
Одним из основных преимуществ использования Nightwatch для компонентного тестирования, помимо наличия того же API, доступного для end-to-end тестирования, является то, что мы можем запускать тесты в реальном браузере, а не в виртуальной DOM среде, такой как JSDOM.
Это позволяет нам использовать Chrome Dev Tools для отладки тестов.
Например, давайте пойдем дальше и добавим оператор debugger
в функцию LoginWithValidCredentials.play
:
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 с открытыми инструментами разработчика Dev Tools. Теперь мы можем установить точку останова в Dev Tools и пройтись по коду.
![Дебагинг с Chrome DevTools Дебагинг с Chrome DevTools](https://habrastorage.org/getpro/habr/upload_files/679/792/62b/67979262ba3a6b37eda87b7a6c905dc5.png)
3.2. Тест логина с сервер исключением
Исходный пример из документации Testing Library также включает тест для случая, когда сервер выбрасывает исключение.
Попробуем написать то же самое в Nightwatch. На этот раз мы будем использовать только функцию test
, так как мы также можем взаимодействовать с компонентом. Как мы упоминали ранее, функция test
выполняется в контексте Node.js и получает в качестве аргумента объект Nightwatch browser
.
Нам также потребуется обновить ответ заглушки сервера, чтобы он возвращал код состояния 500 и сообщение об ошибке. Этого легко добиться, написав тестовый хук preRender
для истории компонента LoginWithServerException
.
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. Запуск теста
Наконец, давайте запустим наш тест. Будет запущен LoginWithValidCredentials и LoginWithServerException компонент историй в Chrome.
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)
Заключение
Вот и все! Вы можете найти полный код этого примера в репозитории GitHub.Не стесняйтесь заходить в Nightwatch Discord, если у вас есть какие-либо вопросы или отзывы.
Также хочу порекомендовать вам бесплатный урок от OTUS, в рамках которого мы узнаем что из себя представляет PlayWright. Почему автотестеры все чаще стали выбирать именно его? Ознакомимся с его документацией, подключим к тестовому проекту и попробуем его особенности на практике.