You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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示例如下:
    // 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
    }
    
    你可以将转换步骤配置到pre-commit钩子或者CI流程中,每次TS类型更新时自动同步JSON Schema,避免手动维护。
  • 步骤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:
    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
    
    测试用例写法和方案一完全一致,该方案的优势是完全对齐前端TS的校验逻辑,不需要维护中间文件,缺点是测试时需要启动Node进程,速度比方案一稍慢,适合类型逻辑复杂、需要严格对齐TS行为的场景。

内容的提问来源于stack exchange,提问作者Edouard Etancelin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.10 21:30:03