如何在Nestjs GraphQL项目中对各类GraphQL组件进行单元测试
NestJS GraphQL 各类组件单元测试方案
1. 自定义Scalar测试
自定义Scalar的核心逻辑都集中在serialize、parseValue、parseLiteral三个内置方法上,不需要启动完整Nest上下文,直接针对三个方法写单测即可:
- 测试逻辑:
- 给
serialize传入业务侧要输出的JS值,校验返回的是否是符合GraphQL规范的输出值 - 给
parseValue传入客户端提交的变量值,校验解析后的JS值是否符合预期 - 给
parseLiteral传入手动构造的GraphQL AST节点,校验解析结果是否正确
- 给
- 示例代码:
import { DateScalar } from './date.scalar'; describe('DateScalar', () => { const scalar = new DateScalar(); it('serialize 应该把日期对象转为时间戳', () => { const date = new Date('2023-01-01'); expect(scalar.serialize(date)).toBe(date.getTime()); }); it('parseValue 应该把时间戳转为日期对象', () => { const timestamp = 1672531200000; expect(scalar.parseValue(timestamp)).toEqual(new Date(timestamp)); }); });
2. 自定义Directive测试
按照指令的作用分为两类,测试方式略有区别:
- 模式指令(仅修改GraphQL Schema结构):手动构造测试用Schema,应用指令后校验生成的Schema结构是否符合预期即可
- 运行时指令(会修改resolver执行逻辑):构造最小化的Nest GraphQL测试模块,注册指令和一个简单的测试用Resolver,发起模拟GraphQL查询后校验返回结果是否符合指令逻辑
- 示例代码:
import { Test } from '@nestjs/testing'; import { GraphQLModule } from '@nestjs/graphql'; import { UpperDirective } from './upper.directive'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { gql } from 'apollo-server-express'; import * as request from 'supertest'; describe('UpperDirective', () => { let app; beforeAll(async () => { const module = await Test.createTestingModule({ imports: [ GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, typeDefs: gql` directive @upper on FIELD_DEFINITION type Query { test: String @upper } `, resolvers: { Query: { test: () => 'hello world' } }, buildSchemaOptions: { directives: [new UpperDirective()] } }) ] }).compile(); app = module.createNestApplication(); await app.init(); }); it('应该把字段返回值转为大写', async () => { const res = await request(app.getHttpServer()) .post('/graphql') .send({ query: `{ test }` }); expect(res.body.data.test).toBe('HELLO WORLD'); }); });
3. Plugins测试
GraphQL插件都是基于Apollo的插件生命周期实现的,直接mock对应生命周期的参数,调用插件的对应钩子方法校验行为即可:
- 测试逻辑:针对你用到的钩子(比如
requestDidStart、willSendResponse等),构造mock的请求上下文、响应对象等参数,传入插件的钩子方法,校验插件的逻辑是否正确执行,比如是否打印了日志、是否修改了响应头等 - 示例代码:
import { LoggingPlugin } from './logging.plugin'; describe('LoggingPlugin', () => { const plugin = new LoggingPlugin(); const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(); it('请求结束后应该打印查询日志', async () => { const mockContext = { request: { query: '{ test }' }, response: { status: 200 } }; const hook = plugin.requestDidStart(mockContext as any); await hook.willSendResponse(mockContext as any); expect(consoleLogSpy).toHaveBeenCalledWith('GraphQL query: { test }'); }); });
4. 异常过滤器(含自定义异常)测试
自定义异常测试
直接实例化异常,校验异常的状态码、错误信息、扩展字段是否符合预期即可。
异常过滤器测试
异常过滤器的核心逻辑都在catch方法内,构造mock的异常对象和Host上下文参数,调用catch方法校验返回的响应内容是否符合预期:
- 示例代码:
import { GqlExceptionFilter } from './gql-exception.filter'; import { BadRequestException } from '@nestjs/common'; import { GqlExecutionContext } from '@nestjs/graphql'; describe('GqlExceptionFilter', () => { const filter = new GqlExceptionFilter(); const mockJson = jest.fn(); const mockStatus = jest.fn(() => ({ json: mockJson })); beforeAll(() => { jest.spyOn(GqlExecutionContext, 'create').mockReturnValue({ getContext: () => ({ res: { status: mockStatus } }) } as any); }); it('应该把BadRequestException转为指定格式的GraphQL错误', () => { const exception = new BadRequestException('参数错误'); filter.catch(exception, {} as any); expect(mockStatus).toHaveBeenCalledWith(400); expect(mockJson).toHaveBeenCalledWith(expect.objectContaining({ errors: expect.arrayContaining([ expect.objectContaining({ message: '参数错误' }) ]) })); }); });
内容的提问来源于stack exchange,提问作者Hantsy
相关产品推荐
相关产品推荐

