如何用Jest测试GraphQL所有字段解析器是否已实现?
问题
我希望编写一个Jest测试,用于检查GraphQL的所有字段解析器是否已实现。假设我有如下TypeGraphQL Schema:
@ObjectType() class Ingredients{ @Field(type => ID) id: string; @Field(type => [Recipe]) recipes: Recipe[]; } @ObjectType() class Recipe { @Field(type => ID) id: string; @Field(type => [String]) ingredients: string[]; @Field(type => Author) author: Author; } @ObjectType() class Author{ @Field(type => ID) id: string; @Field(type => [Recipe]) recipes: Recipe[]; }
若Recipe解析器配置为数据库自动加载所有关联关系,以下查询可成功执行:
query { recipes { id, ingredients { id } author { id } } }
但如果缺少Author字段解析器,以下查询会执行失败:
query { ingredients { id, recipes { id author { id } } } }
请问如何编写不依赖特定查询的测试来校验必要字段解析器是否已实现?另外,是否存在方法让测试自动识别需要字段解析器的字段,比如判断字段是否引用了其他ObjectType?
解决方案
一、自动识别需要解析器的字段
TypeGraphQL自带元数据存储API,可以通过它遍历所有定义的ObjectType及其字段,自动筛选出引用自定义ObjectType的字段(这类字段通常需要解析器,除非ORM自动关联加载):
- 调用
getMetadataArgsStorage()获取TypeGraphQL的元数据仓库 - 提取所有注册的ObjectType名称,存入集合用于后续判断
- 遍历每个ObjectType的字段,检查字段返回类型是否为自定义ObjectType:
- 处理数组类型:如果字段是数组,提取其内部的实际类型
- 对比类型名称是否存在于ObjectType集合中
示例代码:
import { getMetadataArgsStorage } from "type-graphql"; // 收集所有自定义ObjectType的名称 const registeredObjectTypes = new Set( getMetadataArgsStorage().objectTypes.map(type => type.name) ); // 筛选出需要解析器的字段,格式为「类型名.字段名」 const fieldsRequiringResolvers = getMetadataArgsStorage().objectTypes.flatMap(objType => { return objType.fields.filter(field => { let targetType = field.type(); // 处理数组类型,取内部实际类型 if (field.isArray) { targetType = targetType[0]; } // 判断是否为自定义ObjectType return registeredObjectTypes.has(targetType.name); }).map(field => `${objType.name}.${field.name}`); });
二、编写Jest测试校验解析器实现
基于生成的GraphQL Schema,直接检查每个需要解析器的字段是否存在resolve方法:
- 按照业务代码中的逻辑构建Schema,保证测试和生产环境使用相同的Schema配置
- 遍历之前筛选出的字段,逐个校验对应字段的解析器是否存在
示例测试代码:
import { buildSchema, getMetadataArgsStorage } from "type-graphql"; import { IngredientsResolver, RecipeResolver, AuthorResolver } from "./your-resolvers-path"; // 导入项目中的解析器 describe("GraphQL Resolver Validation", () => { let schema; beforeAll(async () => { // 和业务代码一致的Schema构建逻辑 schema = await buildSchema({ resolvers: [IngredientsResolver, RecipeResolver, AuthorResolver], }); }); it("all required field resolvers are implemented", () => { // 复用前面的逻辑获取需要校验的字段 const registeredObjectTypes = new Set( getMetadataArgsStorage().objectTypes.map(type => type.name) ); const fieldsToValidate = getMetadataArgsStorage().objectTypes.flatMap(objType => { return objType.fields.filter(field => { let targetType = field.type(); if (field.isArray) { targetType = targetType[0]; } return registeredObjectTypes.has(targetType.name); }).map(field => ({ typeName: objType.name, fieldName: field.name, })); }); // 检查每个字段的解析器是否存在 const missingResolvers = []; fieldsToValidate.forEach(({ typeName, fieldName }) => { const graphqlType = schema.getType(typeName); if (!graphqlType) { missingResolvers.push(`Type ${typeName} not found in schema`); return; } const targetField = graphqlType.getFields()[fieldName]; if (!targetField.resolve) { missingResolvers.push(`Resolver missing for ${typeName}.${fieldName}`); } }); // 断言无缺失解析器 expect(missingResolvers).toHaveLength(0); }); });
补充说明
- 如果项目使用ORM自动关联加载(比如TypeORM的关联注解),部分字段无需手动编写解析器,可通过自定义装饰器标记这些字段,在测试的筛选逻辑中跳过它们。
- 该方案不依赖具体查询,直接从Schema元数据和生成的Schema对象校验,能覆盖所有需要解析器的字段,避免遗漏边缘场景。
内容的提问来源于stack exchange,提问作者sev
相关产品推荐
相关产品推荐

