如何在RSpec中使用TypeScript类型定义校验API返回值?
RSpec校验后端API返回符合TypeScript类型定义的实现方案
核心思路:Ruby原生无法直接解析TypeScript类型定义,以下提供两种成熟的落地方案,适配不同场景:
方案一:JSON Schema中转方案(推荐,性能好、维护简单)
- 步骤1:批量转换TypeScript类型定义为JSON Schema
使用TS转JSON Schema的工具,将项目中所有前端d.ts类型定义批量转换为JSON Schema文件,统一存放在Rails项目的spec/support/schemas目录下。你给出的Car类型转换后的JSON Schema示例如下:
你可以将转换步骤配置到pre-commit钩子或者CI流程中,每次TS类型更新时自动同步JSON Schema,避免手动维护。// spec/support/schemas/car.json { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "color": { "type": "string" }, "max_speed": { "type": "number" }, "id": { "type": "number" } }, "required": ["color", "max_speed", "id"], "additionalProperties": false } - 步骤2:集成JSON Schema校验到RSpec
首先在Gemfile中添加依赖:
执行gem 'json-schema'bundle install安装依赖后,在spec/support/matchers目录下创建自定义RSpec matcher:RSpec::Matchers.define :be_implemented_by do |type_name| schema_path = Rails.root.join("spec", "support", "schemas", "#{type_name.underscore}.json") schema = JSON.parse(File.read(schema_path)) match do |actual_response| JSON::Validator.validate(schema, actual_response) end failure_message do |actual_response| "返回结果不符合TypeScript #{type_name} 类型定义,错误如下:\n#{JSON::Validator.fully_validate(schema, actual_response).join("\n")}" end end - 步骤3:编写测试用例
按照你期望的写法,测试用例可以直接写成:# request/car_spec.rb it "符合Car类型定义" do # 调用API获取返回结果 get api_v1_car_path(12) car_response = JSON.parse(response.body) # 直接用自定义matcher校验 expect(car_response).to be_implemented_by("Car") end
方案二:直接调用TypeScript编译API校验(无中间文件,100%对齐TS校验逻辑)
如果不想维护JSON Schema中间文件,可以直接在RSpec测试流程中调用Node脚本,用TypeScript原生的类型校验能力做校验:
- 步骤1:编写TS校验脚本
在项目中创建script/validate_ts_type.js文件,内容如下:const ts = require('typescript'); const path = require('path'); // 配置TS类型定义所在目录 const TYPE_ROOT = path.join(__dirname, '../app/javascript/types'); // 初始化TS编译实例 const program = ts.createProgram([`${TYPE_ROOT}/**/*.d.ts`], { strict: true }); const typeChecker = program.getTypeChecker(); // 获取入参:类型名、待校验的JSON字符串 const targetType = process.argv[2]; const inputData = JSON.parse(process.argv[3]); // 查找目标类型定义 let targetTypeNode = null; for (const sourceFile of program.getSourceFiles()) { if (!sourceFile.fileName.includes('.d.ts')) continue; targetTypeNode = sourceFile.statements.find( stmt => ts.isTypeAliasDeclaration(stmt) && stmt.name.escapedText === targetType ); if (targetTypeNode) break; } if (!targetTypeNode) process.exit(2); // 校验类型匹配 const inputType = typeChecker.getTypeOfValueAtLocation(inputData); const isAssignable = typeChecker.isTypeAssignableTo(inputType, typeChecker.getTypeAtLocation(targetTypeNode)); process.exit(isAssignable ? 0 : 1); - 步骤2:编写RSpec自定义matcher
在spec/support/matchers目录下创建对应的matcher:
测试用例写法和方案一完全一致,该方案的优势是完全对齐前端TS的校验逻辑,不需要维护中间文件,缺点是测试时需要启动Node进程,速度比方案一稍慢,适合类型逻辑复杂、需要严格对齐TS行为的场景。RSpec::Matchers.define :be_implemented_by do |type_name| match do |actual_response| system("node script/validate_ts_type.js #{type_name} '#{JSON.generate(actual_response)}'") end failure_message do |actual_response| "返回结果不符合TypeScript #{type_name} 类型定义" end end
内容的提问来源于stack exchange,提问作者Edouard Etancelin
相关产品推荐
相关产品推荐

