ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Supabase 单体仓库中的 Vitest 类型测试实战:expectTypeOf 与 assertType 完全指南

Supabase 单体仓库中的 Vitest 类型测试实战:expectTypeOf 与 assertType 完全指南 Supabase 单体仓库中的 Vitest 类型测试实战expectTypeOf 与 assertType 完全指南【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase本篇基于 Supabase 仓库中的 Vitest 技能参考文档 advanced-type-testing.md系统讲解如何在 TypeScript 项目中用expectTypeOf与assertType做零运行时的类型级断言。Supabase 是一个以 Postgres 为核心的开发平台其官网、文档站、Studio 控制台等前端资产均以 pnpm Vitest 的 TypeScript 单体仓库形式组织读完本文你将掌握类型测试的文件约定.test-d.ts、test.typecheck配置项、expectTypeOf的完整断言 API函数参数、对象形状、品牌类型、泛型、可空类型以及如何把类型测试融入仓库现有的turbo typecheck流水线。为什么需要类型测试运行时单元测试验证的是行为对不对而类型测试验证的是类型契约稳不稳。expectTypeOf和assertType全部工作在类型层面编译后即被擦除不产生任何运行时开销。它们特别适合守护以下契约公共工具函数的参数与返回值签名防止签名被悄悄改宽或改窄API 返回类型如User | null的可空性约定品牌类型Branded Type之间的互斥性防止UserId被误传给PostId参数。仓库背景Supabase 中的 Vitest 版本与测试基线在动手之前先看当前仓库的真实测试基线便于理解本文所有示例的运行前提仓库通过 pnpm catalog 统一管理 Vitest 版本pnpm-workspace.yaml中声明vitest: ^4.1.4以及vitest/coverage-v8: ^4.1.4、vitest/ui: ^4.1.4因此本文 API 基于 Vitest 4.x 语义技能总览文档 SKILL.md 也标注该技能基于 Vitest 3.x 及以上生成类型测试 API 在这一区间保持稳定。根目录 package.json 提供typecheck: turbo --continue typecheck由各包如 packages/pg-meta/package.json、packages/ui-patterns/package.json的tsc --noEmit脚本组成整库类型检查流水线。test-d.ts文件同样会被 tsconfig 纳入因此类型断言失败也会在turbo typecheck阶段暴露。仓库中已经存在真实的expectTypeOf用法packages/pg-meta/test/pg-format.test.ts 首行即import { describe, expect, expectTypeOf, test } from vitest并在第 306、311 行对 SQL 格式化的结果做expectTypeOf(result).toBeString断言——这是一个运行时 类型断言混合测试的现成案例本文最后会展开。Studio 应用的 apps/studio/vitest.config.ts 展示了典型的defineConfig写法globals: true、environment: jsdom、setupFiles、coverage后文的typecheck配置块就挂在同一test键下。准备工作.test-d.ts 文件约定类型测试文件统一使用.test-d.ts扩展名以便与普通运行时测试区分。最简示例// math.test-d.ts import { expectTypeOf } from vitest import { add } from ./math test(add returns number, () { expectTypeOf(add).returns.toBeNumber() })注意两点expectTypeOf直接来自vitest包无需额外依赖断言本身是类型级的test回调在执行期只是空跑真正的校验发生在 TypeScript 编译器对文件做类型检查时。配置test.typecheck在 Vitest 配置中开启类型检查defineConfig({ test: { typecheck: { enabled: true, // Only type check only: false, // Checker: tsc or vue-tsc checker: tsc, // Include patterns include: [**/*.test-d.ts], // tsconfig to use tsconfig: ./tsconfig.json, }, }, })各参数含义如下参数类型说明enabledboolean开启类型测试模式false时.test-d.ts不会被执行类型检查流程onlybooleantrue表示只跑类型检查跳过普通运行时测试对应 CLI 的--typecheck.onlycheckertsc \| vue-tsc底层类型检查器Vue 项目用vue-tsc本仓库这类 React/纯 TS 项目保持默认的tsc即可includestring[]参与类型检查的文件 glob默认匹配**/*.test-d.tstsconfigstring指定使用的 tsconfig 路径保证检查时的编译选项strict、paths等与项目一致对照仓库实际配置apps/studio/vitest.config.ts 目前只配置了globals、environment、setupFiles、coverage等运行期项尚未启用typecheck块这意味着 Studio 的类型保障目前主要来自各包tsc --noEmit的 typecheck 脚本由根级turbo --continue typecheck聚合。若在某个包中新增.test-d.ts文件建议在对应包的vitest.config.ts中按上表补全typecheck块同时确认 tsconfig 的include覆盖到该文件这样tsc --noEmit与vitest typecheck两条通道都能生效。expectTypeOf 基础断言原始类型全集expectTypeOfT()的泛型参数即被测试的目标类型。对原始类型API 提供了一组精确匹配的断言import { expectTypeOf } from vitest // Basic type checks expectTypeOfstring().toBeString() expectTypeOfnumber().toBeNumber() expectTypeOfboolean().toBeBoolean() expectTypeOfnull().toBeNull() expectTypeOfundefined().toBeUndefined() expectTypeOfvoid().toBeVoid() expectTypeOfnever().toBeNever() expectTypeOfany().toBeAny() expectTypeOfunknown().toBeUnknown() expectTypeOfobject().toBeObject() expectTypeOfFunction().toBeFunction() expectTypeOf[]().toBeArray() expectTypeOfsymbol().toBeSymbol()这些断言的失败表现是类型错误而非运行时失败当expectTypeOfnumber().toBeString()不成立时toBeString()的调用本身会产生编译错误因此.test-d.ts文件只要无法通过tscvitest typecheck就会报红。值层面的类型检查除了直接用泛型参数声明类型expectTypeOf也可以以实际值作为输入编译器会推断出该值的类型再做断言const value hello expectTypeOf(value).toBeString() const obj { name: test, count: 42 } expectTypeOf(obj).toMatchTypeOf{ name: string }() expectTypeOf(obj).toHaveProperty(name)这里体现了两个不同力度的断言toMatchTypeOf是结构子集匹配实际类型只需兼容目标类型可多字段toHaveProperty只校验该属性存在不约束其类型。函数类型断言对函数expectTypeOf(fn)暴露parameters参数元组与returns返回类型两个维度function greet(name: string): string { return Hello, ${name} } expectTypeOf(greet).toBeFunction() expectTypeOf(greet).parameters.toEqualTypeOf[string]() expectTypeOf(greet).returns.toBeString() // Parameter checking expectTypeOf(greet).parameter(0).toBeString()parameters.toEqualTypeOf[string]()断言参数是一个恰好只有一个 string 参数的元组可防止参数被新增、改宽或变成可选returns.toBeString()断言返回类型parameter(0)按位置取单个参数断言适合只关心某一参数字段类型的场景。对象类型断言interface User { id: number name: string email?: string } expectTypeOfUser().toHaveProperty(id) expectTypeOfUser().toHaveProperty(name).toBeString() // Check shape expectTypeOf({ id: 1, name: test }).toMatchTypeOfUser()toHaveProperty(name).toBeString()展示了断言链先定位属性再对属性类型断言。toMatchTypeOfUser()用来校验一个具体字面量对象是否长得像User——因为email是可选的{ id: 1, name: test }可以匹配User这正是子集匹配语义的典型应用。相等性 vs 匹配toEqualTypeOf 与 toMatchTypeOf 的语义边界这是类型测试中最重要的区分interface A { x: number } interface B { x: number; y: string } // toMatchTypeOf - subset matching expectTypeOfB().toMatchTypeOfA() // B extends A // toEqualTypeOf - exact match expectTypeOfA().not.toEqualTypeOfB() // Not exact match expectTypeOfA().toEqualTypeOf{ x: number }() // Exact matchtoMatchTypeOf检查的是可赋值性/子集关系B extends A允许目标类型缺少字段toEqualTypeOf检查的是类型完全相等A与B因字段不同而不相等所以必须用not前缀与结构化字面量{ x: number }则相等。not前缀可用于所有断言表达类型应当不等于/不属于……的负向契约。品牌类型Branded Types的互斥性验证品牌类型是同名底层、不同语义类型的标准做法类型测试是验证品牌之间确实互不兼容的最直接手段type UserId number { __brand: UserId } type PostId number { __brand: PostId } expectTypeOfUserId().not.toEqualTypeOfPostId() expectTypeOfUserId().not.toEqualTypeOfnumber()两条断言分别确认UserId与PostId不等价交叉类型中的__brand标记生效且品牌类型不等于裸number品牌真正增加了类型约束。这类断言能防止有人日后把UserId简化回number别名时悄无声息地通过编译。泛型函数的实例化断言function identityT(value: T): T { return value } expectTypeOf(identitystring).returns.toBeString() expectTypeOf(identitynumber).returns.toBeNumber()对泛型函数可以直接用类型实参实例化identitystring后再走returns链用来锁定具体化后的签名。这对像identity这样的透传函数、以及fetchT一类返回PromiseT的 API 包装尤其有用。可空类型断言type MaybeString string | null | undefined expectTypeOfMaybeString().toBeNullable() expectTypeOfstring().not.toBeNullable()toBeNullable()覆盖null或undefined任意一方参与联合的情况。结合负向断言not.toBeNullable()可以双向锁定一个返回类型到底可不可空——对getUser(): User | null这类 API 边界契约非常关键。assertType对值的类型级断言assertTypeT(value)不产生运行时断言仅声明该值必须可赋值给 Timport { assertType } from vitest function getUser(): User | null { return { id: 1, name: test } } test(returns user, () { const result getUser() // ts-expect-error - should fail type check assertTypestring(result) // Correct type assertTypeUser | null(result) })assertTypestring(result)因User | null不可赋值给string而编译失败前面的ts-expect-error则把这个失败转正为一条测试预期——即此处必须报错。用 ts-expect-error 测试代码必须产生类型错误test(rejects wrong types, () { function requireString(s: string) {} // ts-expect-error - number not assignable to string requireString(123) })ts-expect-error的语义是下一行必须报类型错误如果错误出现指令被消费测试通过如果错误消失例如参数类型被放宽为string | number该指令本身变成未使用的 expect-error并导致编译失败。这是把类型收紧当作回归测试的经典技巧。运行类型测试的三种方式# Run type tests vitest typecheck # Run alongside unit tests vitest --typecheck # Type tests only vitest --typecheck.onlyvitest typecheck专门进入类型检查子命令等价于开启test.typecheck.enabled的独立运行vitest --typecheck类型测试与普通测试同批运行适合希望一次拿到全部结果的 CI 场景vitest --typecheck.only只跑类型测试跳过运行时用例。落到本仓库的工程组织上各包的package.json已存在typecheck: tsc --noEmit脚本如 packages/common/package.json、packages/ui-patterns/package.json根级通过turbo --continue typecheck全量聚合见 package.json。因此一个务实的落地组合是本地用vitest typecheck快速迭代单个包的类型断言CI 继续由turbo typecheck兜底确保.test-d.ts文件在两条通道下都被检查。混合测试文件运行时断言与类型断言共存类型测试不必孤立在.test-d.ts中可以直接和普通运行时测试写在同一文件// user.test.ts import { describe, expect, expectTypeOf, test } from vitest import { createUser } from ./user describe(createUser, () { test(runtime: creates user, () { const user createUser(John) expect(user.name).toBe(John) }) test(types: returns User type, () { expectTypeOf(createUser).returns.toMatchTypeOf{ name: string }() }) })Supabase 仓库中就有这一模式的真实用例packages/pg-meta/test/pg-format.test.ts 同时使用expect运行时值断言与expectTypeOf类型断言来测试 SQL 格式化功能——在验证格式化输出内容的同时用类型断言锁定返回值必须是 string这一契约。从源码结构看这种混合写法在该 monorepo 中是可复制的既有实践只要项目devDependencies里通过 pnpm catalog 引入的 vitest 版本支持expectTypeOf当前为^4.1.4任何包的.test.ts(x)文件都可以直接导入使用。关键要点回顾类型专用测试文件使用.test-d.ts扩展名expectTypeOf是类型断言的主入口支持原始类型、函数参数/返回值、对象属性、泛型实例化toMatchTypeOf表示子集匹配可赋值toEqualTypeOf表示完全相等二者语义不可混用assertType用于值必须可赋值给 T的轻量声明ts-expect-error用于把必须产生类型错误固化为回归测试通过vitest typecheck、vitest --typecheck、vitest --typecheck.only三种方式运行并与仓库既有的turbo typecheck流水线互补。【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表