Переход на 4.0
Версия 4.0 делает пакет легче для вашего проекта. Ни один хелпер не удалён, спаи работают как раньше. Самое большее — вы поменяете несколько строк импорта:
// было
import { createSpyFromClass, stubIntersectionObserver } from 'vitest-auto-spy';
// стало
import { createSpyFromClass } from 'vitest-auto-spy';
import { stubIntersectionObserver } from 'vitest-auto-spy/dom-stubs';Что проверить
- Поднимите версию и запустите проверку типов, тесты и линтер.
- Если компилятор не находит DOM-заглушку или хелпер диагностики прогона, поменяйте импорт на
vitest-auto-spy/dom-stubsилиvitest-auto-spy/diagnostics. Это делает за васnpx vitest-auto-spy codemod. Подробно — раздел 2. - Если вы аннотируете
returnSubject()какSubject<T>из rxjs и это перестало компилироваться, добавьтеimport 'vitest-auto-spy/rxjs';в файл, который включён вtsconfig. Подробно — раздел 1. - Если линтер покраснел: теперь каждое правило — ошибка. Настройте три правила или сначала переведите всё в предупреждения. Подробно — раздел 3.
Зачем обновляться
Релиз не добавляет возможностей и не меняет поведение в рантайме. Каждая строка ниже — расход, который ваш проект перестаёт нести.
| Что вы получаете | Измерено |
|---|---|
rxjs уходит из вашей TypeScript-программы. Раньше публикуемые типы упоминали Observable и Subject, и типы rxjs грузились в каждый проект. | программа 303 → 114 файлов; файлов rxjs 189 → 0 |
Публикуемый .d.ts компилируется без установленного rxjs | TS2307 при skipLibCheck: false без rxjs: воспроизводилось → исчезло |
| Каждый файл спеки, который не трогает DOM-глобалы, стартует быстрее | −0,159 мс на файл; корневая точка входа 15,5 → 12,9 кБ min+gzip |
| Вторая копия rxjs больше не ломает observable-спаи | nextWith is not a function без намёка на дубликат → хелперы на месте |
| Строгость правил линтера выбирает ваш проект | все девятнадцать правил — error; на странице каждого правила сказано, как смягчить |
Цена — два пути импорта плюс один импорт, если вы аннотируете returnSubject().
Что изменилось
Три ломающих изменения. Ни одно не меняет того, что делает спай:
- ни один хелпер не удалён и не переименован;
- ни один ключ конфигурации не поменял смысл;
- поведение в рантайме прежнее.
Поменялось, откуда импортируются две группы хелперов, тип returnSubject() и строгость правил линтера.
| Что делать | |
|---|---|
| 1. rxjs ушёл из деклараций | обычно ничего; один импорт, если вы аннотируете returnSubject() |
| 2. DOM-заглушки и диагностика прогона переехали | поменять импорт у тринадцати хелперов во всех точках входа, которые реэкспортируют ядро |
| 3. Каждое правило линтера — ошибка | ничего или один блок конфигурации |
1. rxjs ушёл из публикуемых деклараций
В рантайме rxjs всегда был необязательным: его импортирует только vitest-auto-spy/rxjs. С типами было иначе. Публикуемые декларации начинались со строки import { Observable, Subject } from 'rxjs'. TypeScript загружает импорт одних типов так же, как любой другой импорт. Поэтому:
- каждый проект загружал типы rxjs, даже если rxjs не использовал;
- проект без rxjs получал
TS2307внутри.d.tsпакета приskipLibCheck: false.
Замер на опубликованном пакете, для проекта, который только вызывает createSpyFromClass на сервисе с промисами:
| 3.18 | 4.0 | |
|---|---|---|
| файлов в программе TypeScript | 303 | 114 |
из них .d.ts-файлов rxjs | 189 | 0 |
TS2307 при skipLibCheck: false без установленного rxjs | да | нет |
Почему не помогает import type — на странице про rxjs.
Касается ли это вас?
Запустите проверку типов. Она может пожаловаться на две вещи; часто встречается только первая.
returnSubject() получает тип SubjectLike, если слоя rxjs нет в программе
returnSubject() и nextWithPerCall() теперь возвращают SubjectOf<T>:
- Если
vitest-auto-spy/rxjsвходит в программу, в которой TypeScript проверяет спеки,SubjectOf<T>— это и естьSubject<T>из rxjs. - Если не входит — это структурный
SubjectLike<T>:next,error,completeиasObservable.
Во втором случае такая аннотация перестаёт компилироваться:
const subject: Subject<Product[]> = service.getProducts$.returnSubject();
// ~~~~~~~ Type 'SubjectLike<Product[]>' is not assignable to type 'Subject<Product[]>'Чинится одной строкой в файле, который включён в tsconfig:
import 'vitest-auto-spy/rxjs';Этот импорт и так нужен observable-хелперам в рантайме. Если тесты вызывают nextWith, он у вас где-то есть. Ошибка значит, что этот файл не видит компилятор. Обычно это файл из setupFiles Vitest, который не входит ни в один tsconfig. Добавьте его в include или перенесите импорт в спеку.
Под @angular/build:unit-test 22.2.0 и новее одного include мало. Билдер компилирует только спеки, providersFile, setupFiles и .d.ts-файлы из tsconfig.spec.json. Поставьте импорт в setup-файл, в спеку или в .d.ts.
Если вы не аннотируете результат (const subject = spy.load.returnSubject()), для вас ничего не меняется.
Распознавание структурное, так что продублированный rxjs теперь подходит
Член класса считается observable, если в его типе есть subscribe и forEach(next), который возвращает промис.
- Подходят:
Observableиз rxjs, любойSubject,EventEmitterиз Angular. - Не подходят:
Promise, массивы,Signal,OutputEmitterRefиз Angular.
Поэтому обычный спай случайно не станет observable-спаем.
Один случай стал лучше: Observable из второй копии rxjs в node_modules. Раньше он оставался без observable-хелперов, и тест падал с nextWith is not a function. Теперь всё работает.
Если у вашего собственного типа потока случайно есть оба члена, он тоже считается observable. Если для вашего типа это неверно, назовите член в methodsToSpyOn или onlyMethodsToSpyOn.
2. DOM-заглушки и диагностика прогона переехали на свои подпути
Тринадцать хелперов ушли из всех точек входа, которые реэкспортируют ядро: vitest-auto-spy, /bun, /bun-angular, /node, /react, /vue и /svelte. Импорт из /react вместо корня от этого не спасает. Сами хелперы не изменились — поменялся только путь импорта:
| Было | Стало |
|---|---|
stubIntersectionObserver, stubResizeObserver, stubMutationObserver, stubObserver, intersectionEntry, resizeEntry, mutationRecord, stubMediaElement, stubAbortController | vitest-auto-spy/dom-stubs |
compareTestRuns, summarizeTestRun, formatTestRunComparison, diffByField | vitest-auto-spy/diagnostics |
mockConstructor, stubConstructor и createSpyClass остались в корне: они про new, а не про DOM. restoreMockedProps() и setupAutoSpy() по-прежнему откатывают всё, что патчат перенесённые заглушки.
Их типы переехали вместе с ними:
- из
/dom-stubs:ObserverStub,ObserverStubOptions,ObserverGlobal,ObserverInstance,IntersectionObserverStubOptions,MutationRecordInit,ResizeEntryRect,MediaElementState,MediaElementStub,MediaElementStubOptions; - из
/diagnostics:TestRunComparison,TestRunReport,TestRunSummary.
// было
import { createSpyFromClass, stubIntersectionObserver } from 'vitest-auto-spy';
// стало
import { createSpyFromClass } from 'vitest-auto-spy';
import { stubIntersectionObserver } from 'vitest-auto-spy/dom-stubs';Компилятор найдёт все такие места. npx vitest-auto-spy codemod перепишет импорты. Он читает карту exports установленного пакета, поэтому каждое имя попадает в ту точку входа, где оно на самом деле есть.
Почему. Раннер не делает tree-shaking файлов спек, а реэкспорт в ESM всегда загружает свой модуль. Поэтому каждый файл спеки — даже в Node-сервисе без DOM — исполнял 27 кБ кода про observer'ы, медиаэлементы и AbortController, чтобы получить createSpyFromClass. Единственное решение — убрать этот код из корня.
Замер на собранном пакете, отдельный процесс на каждый замер, медианы 40 парных прогонов:
| изменение | |
|---|---|
| файл спеки, который их не импортирует | −0,159 мс |
файл спеки, который импортирует /dom-stubs | +0,155 мс |
файл спеки, который импортирует /diagnostics | +0,069 мс |
dist | −20,3 кБ JS, −3,8 кБ деклараций |
Обе новые точки входа собраны одним файлом, поэтому импорт добавляет один модуль, а не четыре. При сборке чанками вторая строка показывала +0,62 мс. Если DOM-заглушки импортирует меньше половины ваших файлов спек, весь прогон становится быстрее.
3. Каждое правило ESLint — ошибка
Раньше configs.recommended ставил своим девятнадцати правилам error, warn или off. Теперь все они error. Исключение — правило, добавленное позже: prefer-render-shallow приходит как warn, потому что сообщает о цене, а не об ошибке. Почему — на странице плагина.
На warn сборка не останавливается. В репозитории, где вывод линтера никто не читает, warn работает как off. Какие находки блокируют мерж — решение вашего проекта, и это одна строка конфигурации. Поэтому по умолчанию всё строго, а как ослабить — в разделе Настройка под ваш проект.
У двух правил поменялась не только строгость:
- До 4.0
prefer-native-spy-apiбыло выключено (off). Теперь оно включено, потому что большинство проектов не находятся посреди переезда. Проект, который переезжает, выключает его одной строкой (ниже). jasmine-namespace-without-entryбольше не жалуется наspy.mock.calls[0]. Раньше оно отмечало это обычное для Vitest чтение на уровнеwarn, хотя голыйspy.mock.callsпроходил. Теперь всё, что находится под.mock, правило не трогает.
Если первый прогон красный, сначала посмотрите на эти три правила. Каждое может пожаловаться на правильный код:
import autoSpy from 'vitest-auto-spy/eslint-plugin';
export default [
{
files: ['**/*.spec.ts'],
...autoSpy.configs.recommended,
rules: {
...autoSpy.configs.recommended.rules, // разверните спредом, иначе голый ключ `rules` заменит всю карту
// слой jasmine ставится в setup-файле, который не импортирует ни одна спека: назовите этот файл
'vitest-auto-spy/jasmine-namespace-without-entry': ['error', { setupModules: ['./test-setup'] }],
// жалуется на рабочий переходный код: выключено, пока переезд с jasmine-auto-spies не закончен
'vitest-auto-spy/prefer-native-spy-api': 'off',
// опций нет; правило молчит там, где не может прочитать все провайдеры файла.
// Выключайте его только в файлах, где провайдер регистрирует вызываемый вами хелпер.
// 'vitest-auto-spy/no-unregistered-inject-spy': 'off',
},
},
];В большом существующем проекте начните с того, что все правила — предупреждения, и чините партиями. Рецепт — в разделе Настройка под ваш проект.
Что ещё изменилось в 3.x
Если вы переходите с ранней 3.x, вас коснутся и эти изменения.
- 3.1.0:
gettersToSpyOnиsettersToSpyOnпринимают геттер, который возвращаетSignal. Раньше такой геттер нельзя было назвать. Ошибка звучала какType 'string' is not assignable to type 'never', и о сигналах в ней не было ни слова. - 3.5.0: если назвать одну половину пары get/set, спай ставится на обе. Так происходит, только когда класс объявляет обе. Раньше запись уходила в пустой сеттер и терялась, а
accessorSpies.setters.xбылundefined. - 3.5.0: observable-спай больше не переносит значения в следующий тест.
nextWith(value)из одного теста раньше попадал в следующий тест прежде егоthrowWith(error). Код под тестом тогда шёл по ветке успеха со старыми данными. Послеerror()илиcomplete()следующие вызовыnextWithничего не выпускали. ТеперьresetAutoSpyсбрасывает и то и другое. Для спая, общего для нескольких тестов, вызывайтеresetAutoSpy(spy)вbeforeEach. - 3.5.0:
expectEmissionиexpectEmissionsвыводят тип эмиссии.expectEmission(of(1))— этоPromise<number>. Раньше оба хелпера возвращалиPromise<unknown>, и потеря типа всплывала, только когда спека читала поле результата. - 3.5.0: подмены из
createAutoMockиmockDeepправильно отвечают на три операции.- Четыре хелпера
mock*Propработают на них, аrestoreMockedProps()откатывает патч. Раньше патч молча игнорировался. delete mock.optionalMethodубирает член, пока в него снова что-нибудь не запишут. Раньше следующее чтение создавало новый спай.of(mock)выпускает подмену. Раньше rxjs принимал подмену за планировщик, и поток оставался пустым.
- Четыре хелпера
- 3.5.0:
asInstanceпринимает результатmockDeep. Раньше глубокий мок нельзя было передать туда, где ждутT. - 3.5.0:
prefer-inject-spyчитает форму в две строки. Раньше оно отмечало толькоvi.spyOn(TestBed.inject(X), 'm'), записанный в одну строку. - 3.5.0:
provideAutoSpyпринимает абстрактные классы-токены.abstract class LocalStorageс провайдером{ provide: LocalStorage, useClass: BrowserLocalStorage }раньше не работал дважды: вызов без настроек давал пустой спай, а вызов с настройками не компилировался (TS2345: Cannot assign an abstract constructor type to a non-abstract constructor type). Теперь работают оба. Самописный{ provide: LocalStorage, useValue: createAutoMock<LocalStorage>() }больше не нужен. - 3.5.0: оба хелпера-провайдера принимают
returns,overridesиobservablePropsToSpyOn. РаньшеprovideAutoSpyForTokenпринимал только значения свойств, аprovideAutoSpy— только настройку методов, так что спай, которому нужно и то и другое, донастраивали вbeforeEach. - 3.5.0:
overrideProvider(X, provideAutoSpy(X))работает. Старая документация называла это молчаливым no-op.overrideProviderчитаетuseValueи игнорирует лишнийprovide, так что спай ставится.overrideAutoSpyпо-прежнему понятнее. - 3.7.0:
stable()даёт фикстуре 2000 мс, а затем бросает ошибку с причиной. Раньше фикстура, которая не успокаивалась, висела, пока Vitest не сообщал о таймауте файла в 5 с, где не были названы ни хелпер, ни фикстура. - 3.13.0:
nextWithValuesвыпускает ложные значения.{ value: false },{ value: 0 },{ value: '' },{ value: null }и ложныйerrorValueраньше не выпускали ничего. Поток булевых значений или счётчика молчал. - 3.13.0: спай метода принимает только те аргументы, что и настоящий метод. Раньше на спае
read(key: string)компилировались иread(1), иread('ok', 'extra'), иread(). Теперь API мока —MockInstance, у которого нет своей сигнатуры вызова. - 3.7.0: документация больше не показывает
httpResource()сflushEventLoopUntil. Этот пример никогда не работал. Хелпер прокручивает цикл событий и никогда не делает tick, поэтому ресурс не отправлял запрос, и ожидание заканчивалось таймаутом. ИспользуйтеsettleResource()изvitest-auto-spy/angular. - Исправление документации (2026-08-29):
vi.mock('@angular/core')работает под@angular/build:unit-test. Раньше документация говорила, что его нельзя замокать вовсе. Замер на 11 файлах спек в одном прогоне, на@angular/build21.2.16 и 22.1.6: работает; настоящее правило уже.
Что не изменилось
- Ни один хелпер не удалён, не переименован и не помечен устаревшим. Две группы переехали на новые пути импорта, и это всё.
- Ни одно правило линтера не добавлено и не удалено. Кроме исправления
.mockвjasmine-namespace-without-entry, ни одно не поменяло, на что жалуется. Поменялась только строгость. nextWith,nextWithValues,nextOneTimeWith,throwWith,complete,observablePropsToSpyOnи цепочкиcalledWithработают как раньше.rxjsостаётся необязательной peer-зависимостью. Её импортируют толькоvitest-auto-spy/rxjsиvitest-auto-spy/observer-spy— теперь и в типах, и в рантайме. Сборка падает, если rxjs импортирует любая другая точка входа.- Точки входа для Angular, NestJS, React, Vue, Svelte, Bun и
node:testне изменились.
Если вы идёте издалека
- Переход на 3.0 — одна строка в
package.json: peer-диапазонvitestтеперь совпадает с тем, что нужно типам. - Переход на 2.0 важен, если вы ещё на 1.x.
methodsToSpyOnперестал убирать спаи: в одном перенесённом компоненте падающих тестов стало 4 вместо 147. Ленивые спаи методов сократили большой прогон с 257 мс / 425 МБ до 27 мс / 35 МБ.