The Jerusalem PostLikud braces for turnout battle, divided over Netanyahu's chances on eve of Election Day - analysisESPNAre the Lions, Seahawks, Bengals and Giants on the right track?PunchEkiti education agency graduates 76, seeks more adult learnersESPN DeportesF1 ¿Qué necesita Kimi Antonelli para ser campeón en México?Bollywood HungamaCBFC faces reported data security scare as confidential documents linked to over 500 films are allegedly exposedDaily MaverickCYBER SAFETY: SA Human Rights Commission urges stronger laws to protect children onlineStraits Times SportScotland name squad for Nations ChampionshipZDF heuteEntdecken Sie das ZDF-NachrichtenstudioIl Fatto QuotidianoSinner ancora in vetta, ma ora Zverez può superarlo. Musetti non guadagna posizioni dopo la vittoria a Lione | La nuova classifica AtpGolem.deKurioses Gefährt: Polizei stoppt Sofa auf Lime-RollernRai NewsElezioni in Israele, la sfida è tutta su Netanyahu: proteste e sondaggi indicano uno stalloDeadline‘The Lunchbox’ Director Ritesh Batra Sets Saif Ali Khan, Vidya Balan & Mélanie Laurent For ‘Long Years Ago’; Filming Begins In Mumbai
The Daily Newsstand · Free, Always
Monday, October 26, 2026

С Karma на Vitest: как мы обновляли unit-тесты Angular и починили Allure

Translate

Привет! На связи Дмитрий Чепайкин, старший Angular-разработчик в команде Klecks — платформы для разметки данных AI Центра Т-Банка.

Долгое время unit-тесты фронтенда у нас крутились на Karma + Jasmine. Схема работала, пайплайн зеленый, переписывать «ради моды» не хотелось. Но в какой-то момент Karma официально признали deprecated, а корректная отгрузка отчетов в Allure перестала быть чем-то, на что можно положиться. Расскажу, как мы выбирали замену, почему сначала смотрели на Jest, а в итоге приняли Vitest — и как устроен итоговый сетап.

Триггеры миграции

Во фронтенде Klecks три тестовых проекта: customer, operator и shared. Кодовая база включает сотни *.spec.ts, использует Angular TestBed, моки сервисов, фейковые таймеры и кастомные хелперы вроде UnhandledCatcher. При этом интеграционные сценарии уже давно выполнялись на Playwright, а unit-тестирование продолжало опираться на классический Angular-стек: Karma через builder @angular-devkit/build-angular:karma, Jasmine в качестве тестового API и Chrome Headless для запуска в CI. Покрытие кода собиралось через karma-coverage, а результаты unit-тестов пытались выгружать в Allure вместе с e2e-тестами.

Формально все работало. Но стоимость поддержки стека росла, а на Karma уже не решалась надежно одна из задач качества — нормальная иерархия unit-тестов в Allure. Поэтому стоял вопрос не «Нужен ли нам новый раннер?», а «Какой раннер позволит уйти с deprecated-инструмента и при этом не развалить существующие тесты и отчетность?».

Зачем трогать то, что работает

Karma справлялась со своими задачами годами. Руки до миграции не доходили не потому, что стек был идеален, а потому, что он был «достаточно хорош». Локально ng test открывал браузер, в CI Chrome Headless прогонял suites. Менять инфраструктуру при живом продукте — всегда риск.

Мы собрали список проблем.

Deprecated и тупиковая поддержка. Karma больше не развивается как основной современный test runner, поэтому дальнейшая интеграция с актуальным инструментарием требует все больше поддержки legacy-слоя. Оставаться на Karma — значит тащить устаревающий слой между CLI и тестами, который рано или поздно упрется в несовместимость с новой версией Angular/Webpack/Vite.

Allure для unit-тестов. Для e2e у нас уже был рабочий контур Playwright → Allure. Для unit на Karma отчеты не собирались, так как не было стабильного плагина, полноты и стабильности в выгрузке артефактов тестов. В CI это превращалось в «тесты зеленые, а в Allure unit-слой неполноценный». Именно эта боль стала триггером: отчетность — часть процесса, а не опциональный бантик.

Тяжелый браузерный рантайм в CI. Karma поднимает реальный Chrome. Для части DOM-тестов это плюс, но для сотен unit-спеков — лишняя тяжесть образа, флаки по браузеру и более дорогой пайплайн. Cypress-образ ради unit-прогона сам по себе сигнал, что инфраструктура разъехалась с задачей.

Jasmine-специфика. API привычный, но экосистема вокруг моков, таймеров и матчеров все сильнее расходится с тем, что используют Jest/Vitest-мир и современные Allure-репортеры. Любая новая интеграция требовала прокладок под Karma и Jasmine, а не готового first-class-плагина.

Основными целями миграции стали отказ от устаревшего Karma, обеспечение корректной выгрузки результатов unit-тестов в Allure и сохранение существующей бизнес-логики тестов. Изменения должны были затронуть только инфраструктуру тестирования и API тестового раннера.

Рассматривали: Jest vs Vitest

Мы не прыгали сразу в финальный выбор. Сначала на ветке test_jest собрали полноценный прототип на Jest: jest-preset-angular, отдельные конфиги создали на customer / operator / shared, allure-jest, добавили адаптеры под Jasmine-совместимые хелперы (expectAsync, toBeTrue / toBeFalse, глобальный spyOn). Это был честный spike: можно ли перенести сотни спеков и закрыть Allure.

Эксперимент с Jest показал, что можно закрыть основные боли:

  • нативный Node + jsdom без Chrome;

  • официальный путь через jest-preset-angular;

  • рабочий allure-jest с иерархией лейблов;

  • параллельный прогон проектов.

Но именно для нашего Angular-монорепо появились минусы:

  • отдельный контур мимо Angular builder — скрипты jest --config ... вместо ng test;

  • свой зоопарк конфигов (create-jest-config, tsconfig.jest.json, project configs);

  • необходимость compatibility-слоев под старый Jasmine API;

  • трансформ ESM/CJS-зависимостей (transformIgnorePatterns на длинный список пакетов Taiga/Angular);

  • два мира инструментов: CLI/сборка на одном стеке, тесты — на другом.

Jest как технология нас не провалил. Он показал, что миграция с Karma возможна и что Allure на Node-раннере реально оживает. Но как целевой стек для Angular 21 он выглядел компромиссом, а не финальной точкой.

В итоге мы выбрали Vitest. После Jest-spike мы собрали решение на Vitest — коммит с принятым направлением: миграция builders, конфигов и спеков. Выбор опирался на набор практических критериев.

Интеграция Vitest через Analog позволила сохранить запуск через Angular CLI и стала подходящим вариантом для нашего текущего workspace. Vitest подключается через @analogjs/vitest-angular:test. Для разработчика вход тот же:

ng test customer
ng test operator --configuration=ci

В angular.json вместо karma-builder:

"builder": "@analogjs/vitest-angular:test",
"options": {
"configFile": "vitest.config.mts"
}

Мы остаемся внутри Angular CLI, а не уходим в параллельную систему запуска.

Миграция на Vitest для нас закрыла ряд вопросов.

Vite-based pipeline вместо отдельного Jest transform layer. Vitest работает на Vite: быстрее старт, нативная работа с ESM, проще inline зависимостей Angular/Taiga через server.deps.inline. Там, где в Jest копился transformIgnorePatterns, в Vitest мы описываем resolve/alias и список пакетов для инлайна — и это лучше ложится на текущий фронтенд-стек.

Прямая интеграция с Allure через allure-vitest. allure-vitest/reporter пишет результаты в allure-results/unit/<project>. Плюс общий setup автоматически проставляет иерархию: parentSuite → suite → layer → epic → subEpic.

То есть unit-слой наконец встает рядом с интеграционными отчетами по тем же правилам качества.

Меньше «клея», ближе к целевому API. Vitest из коробки дает vi.fn, vi.spyOn, fake timers, globals. Jasmine → Vitest — механическая замена в спеках, без отдельного compatibility-runtime, который пришлось бы сопровождать на Jest.

CI без браузера и с контролем воркеров. Unit-джоба переехала на Node-образ. Воркеры ограничены явно: в CI VITEST_MAX_WORKERS=2, потому что Angular + jsdom + coverage прожорливы по RAM, а K8s часто отдает чужой CPU count. Это осознанная настройка под нашу инфраструктуру, а не дефолт «съешь все ядра».

Вывод: Jest доказал миграцию, Vitest лучше сел как целевой раннер для Angular-приложений Klecks.

Миграция самих тестов

Большая часть работы — не переизобрести тесты, а перевести API. Вот типичные замены.

Было (Jasmine)

Стало (Vitest)

jasmine.createSpy()

vi.fn()

spyOn(obj, 'method')

vi.spyOn(obj, 'method')

jasmine.createSpyObj(...)

ручной stub / vi.fn на методах

and.returnValue(x)

mockReturnValue(x)

and.callFake(fn)

mockImplementation(fn)

calls.reset()

mockClear() / mockReset()

jasmine.clock().install()

vi.useFakeTimers()

jasmine.clock().tick(n)

vi.advanceTimersByTime(n)

jasmine.objectContaining

expect.objectContaining

done()-колбэки

async/await + Promise

jasmine.Spy в типах

Mock из vitest

TestBed, ComponentFixture, провайдеры и структура describe/it почти не менялись. 

Важно, что миграция раннера не должна была стать рефакторингом продуктового кода. Мы отдельно подтянули инфраструктурные хелперы. Например, UnhandledCatcher научили ловить и window.onerror, и rxjs.config.onUnhandledError. В jsdom/Vitest необработанные ошибки Observable ведут себя иначе, чем в Karma/Chrome. Такие места всплывают только на реальном прогоне, поэтому spike на Jest тоже был полезен: часть проблем API мы увидели еще до финального выбора. 

Allure: как закрыли исходную боль

Что мы сделали по шагам: 

  1. Репортер пишет в allure-results/unit/<project>.

  2. Автолейблы строят иерархию из пути файла и имени теста.

  3. Epic выбирается по расположению: customer / operator / shared.

  4. Локально можно собрать и открыть отчет теми же npm-скриптами, что и для integration

  5. Параллелизм: Chrome Headless / Karma → Vitest threads, maxWorkers=2

В итоге unit-слой перестал быть «второй реальностью» относительно Playwright: и там и там Allure понимает слой, suite и продуктовый epic.

Что принесла миграция на vitest. 

Было

Стало

Раннер

Karma + Jasmine + Chrome

Vitest + jsdom (+ Analog builder)

Статус стека

deprecated / legacy path

Актуальный Angular-friendly-путь

Запуск

ng test через karma-builder

ng test через @analogjs/vitest-angular:test

Allure unit

Ненадежная/неполная выгрузка

allure-vitest + автолейблы

CI-образ

Тяжелый browser-based

Node 22

Параллелизм

Браузерный single-run контур

threads + явный лимит воркеров

Скорость джобы в CI

~3 минуты 40 секунд

~2 минуты 20 секунд

Плюсы, которые мы получили:

  • ушли с deprecated Karma;

  • сохранили привычный DX через ng test;

  • unit-отчеты в Allure стали частью нормального процесса;

  • CI стал проще по зависимостям (без Chrome ради unit);

  • спеки мигрируют механически, без переписывания сценариев;

  • ускорили джобу unit тестов в CI на ~36,4%.

Но есть и компромиссы. Они для нас— разумная цена за поддерживаемый стек и рабочую отчетность. Компромиссы, о которых стоит знать:

  • jsdom ≠ полный браузер: редкие DOM/CSS edge-cases могут вести себя иначе;

  • в CI нельзя бездумно крутить maxWorkers: coverage + Angular TestBed легко упираются в память;

  • часть хелперов (UnhandledCatcher, fake timers) потребовала точечной адаптации под новый рантайм.

Нативный Angular Vitest (@angular/build:unit-test) мы сознательно не брали, потому что для нашего кейса он слишком закрытый. Официальный builder заточен под zero-config: Angular сам поднимает Vitest, а кастомный vitest.config либо ограничен, либо поддерживается частично.

Команда Angular прямо говорила: если нужен полноценный vitest.config.ts (плагины, репортеры, тонкая настройка), берите Analog.

Для нас такой уровень контроля был принципиален. Требовалось интегрировать allure-vitest/reporter с собственной иерархией лейблов, создать единый factory-конфиг для customer, operator и shared с общими алиасами, путями Less и директориями покрытия. Кроме того, необходимо было управлять количеством воркеров через VITEST_MAX_WORKERS, чтобы учитывать ограничения памяти в K8s при запуске Angular-тестов с jsdom и coverage, а также гибко настраивать параметры рантайма, включая isolate, inline deps и setupFiles.

Analog (@analogjs/vitest-angular) дает полный контроль «из коробки» и при этом оставляет ng test. Jest-spike уже показал, что «просто зеленых тестов» недостаточно — нужна управляемая инфраструктура и отчеты.

Native имеет смысл для зеленого поля или простых приложений. Для нашего монорепо с Allure и жестким CI Analog оказался практичнее. Позже, когда @angular/build:unit-test догонит по конфигурируемости, можно будет пересмотреть — но на момент решения Analog закрывал требования лучше.

Заключение

Мы не меняли раннер, потому что «все переходят на Vitest». Триггером стала связка из двух фактов: Karma deprecated и Allure для unit на текущем стеке больше не закрывал требования качества.

Jest на ветке test_jest подтвердил, что Node-раннер + Allure — рабочий путь. Vitest выбрали как целевое решение, потому что он лучше стыкуется с Angular CLI через Analog, естественнее живет с Vite/ESM и дает прямую интеграцию с Allure через allure-vitest без отдельной параллельной инфраструктуры запуска.

Итог миграции:

  • один shared Vitest-конфиг на три проекта;

  • builder в angular.json без Karma;

  • автолейблы Allure в @klecks/testing;

  • механический перевод Jasmine API → Vitest в спеках;

  • CI на Node с ограниченными воркерами;

  • unit-тесты в CI проходят на ~36,4% быстрее;

Если у вас похожий Angular-монолит на Karma и уже болит Allure/поддержка, имеет смысл сначала сделать короткий spike на Jest или Vitest именно с выгрузкой отчетов, а не только с «тесты зеленые локально». У нас зеленость никогда не была главной проблемой. Главным было сделать так, чтобы unit-слой снова был наблюдаемым и поддерживаемым.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.