Какова роль юнит тестов при использовании TypeScript
Что проверяют
Не противопоставляете ли вы типы и тесты. Интервьюер хочет услышать, где заканчивается то, что ловит компилятор, и что остаётся на тестах. Следом часто спрашивают про стек тестирования и про то, проверяет ли ваш тест-раннер типы.
Как отвечать
Типы доказывают, что данные нужной формы попадают в нужное место. Они не доказывают, что код делает правильное: функция sum, которая возвращает a - b, пройдёт tsc без единого замечания. Отсюда разделение труда:
- компилятор: опечатки в полях, неверные аргументы, забытые ветки
switchчерез проверку наnever,nullтам, где его не ждут приstrict; - юнит-тесты: поведение, граничные значения, ветвления бизнес-логики, форматирование, редьюсеры, хуки и утилиты;
- рантайм-валидация: типы стираются при сборке, поэтому ответ API,
JSON.parseиlocalStorageкомпилятор не проверяет. Здесь нужна схема, например на Zod, и тест на неё.
Практический плюс TypeScript для тестов: проверки вида «передали строку вместо числа» уже закрыл компилятор, и тесты становятся короче и осмысленнее. Рефакторинг идёт в две ступени: сначала tsc показывает все места, где сломался контракт, потом тесты подтверждают, что поведение не изменилось.
По инструментам: Vitest работает с TypeScript из коробки, в Jest подключают ts-jest или трансформер на SWC. Для компонентов берут React Testing Library, для типизированных моков vi.mocked(). Для библиотек со сложными дженериками есть проверка самих типов через expectTypeOf.
Где спотыкаются
Говорят, что со строгим TypeScript юнит-тесты почти не нужны. Это верно только для кода без логики, а во фронтенде логики хватает: фильтры, расчёты, состояния формы.
Думают, что раннер проверяет типы. Vitest, @swc/jest и Babel типы просто вырезают, поэтому тест с ошибкой типов остаётся зелёным. В CI нужен отдельный шаг tsc --noEmit.
Глушат типы в тестах через as any в моках. Тогда после изменения сигнатуры сервиса мок продолжает возвращать старую форму данных, и тест проходит, хотя реальный код уже сломан.