Зарелізив дві бібліотеки для тестування UI-компонентів: dom-harness та mui-harness
В Google я постійно писав юніт-тести для фронта й основою було використання test harness. Я забув про біль від querySelector в тестах, type casts усюди, від того, що десь з'явився зайвий div, який ломає тести.
Що таке Test harness?
Це тонка обгортка навколо DOM-елемента, яка ховає селектори та взаємодії за чистим API. Саме так тестують компоненти в Angular CDK. Чомусь в світі React такого немає.
✳ dom-harness - базова бібліотека (~110 рядків), на основі якої ви будуєте харнеси для своїх компонентів. Працює з будь-яким фреймворком (є приклади в репі для React, Vue, Svelte, Angular, Solid, Preact). Під капотом - реальний DOM, реальні події, нічого замоканого.
❌ Як виглядає тест без харнесів:
const input = document.querySelector(
'[data-testid="text-input"][name="username"]'
) as HTMLInputElement;
const button = document.querySelector(
'[data-testid="button"]'
) as HTMLButtonElement;
await user.type(input, 'testuser');
await user.click(button);
✅ З харнесами:
const form = LoginFormHarness.first();
await form.usernameInput.type('testuser');
await form.submitButton.click();
Основна ідея, що для кожного компонента робиться харнес, який дає доступ до можливостей компонента через свій API. Тобто це, як Enzyme (який вмер вже), але працює з реальним DOM. Хоча найближча аналогія це PageObject патерн, але той не мав можливості композиції, а тут це основа. Тобто, компоненти вкладені один в одний й відповідно харнеси теж: LoginFormHarness містить TextInputHarness і ButtonHarness, з автоматичним скоупінгом запитів до this.root.
✳ mui-harness - колекція готових харнесів для Material UI: TextField, Button, Checkbox, Dialog, IconButton й інші. Побудований поверх dom-harness. Більше не потрібно лізти в MUI-шну розмітку з . MuiInputBase-root та іншими внутрішніми класами, які ламаються між версіями.
Коли це корисно:
- якщо ви пишете тести для фронту або хочете, щоб ШІ писав їх за вас й це було структуровано.
- якщо у вас в компанії є дизайн-система, то харнес до кожного компонента це взагалі must have.
- Команди, що мігрують між фреймворками. Якщо різні фреймворки рендерять ту саму розмітку, то харнеси не змінюються (тільки render).
- Якщо ти автор UI-бібліотеки, то мати харнеси до своїх компонентів це як types для TypeScript - без них можна, але з ними бібліотека стає значно зручнішою для тестування.
Отримав фідбек, що не відразу зрозуміло, що це таке. Тому написав три документи з детальним роз'ясненням концепцій:
1. https://github.com/koorchik/dom-harness/blob/master/docs/GETTING_STARTED.md
2. https://github.com/koorchik/mui-harness/blob/master/docs/GETTING_STARTED.md
3. https://github.com/koorchik/mui-harness/blob/master/docs/BEST_PRACTICES.md
Також заціність, як виглядає компонет форми логіну (й відповідно харнес й тест до нього) - https://github.com/koorchik/mui-harness/tree/master/examples/login-page/LoginPage/LoginForm
Чи зрозуміла ідея підходу? Діліться думками в коментарях.
PS: писав все AI-friendly, щоб агент міг зрозуміти, як цим користуватися. Також додав пакети до context7 індексу