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

TypeGraphQL+Typegoose+Express应用单元测试方案咨询

针对Express + TypeGraphQL + TypeGoose栈的单元测试落地方案

前置依赖与环境配置

先安装测试相关依赖,执行命令:
npm i -D jest ts-jest @types/jest mongodb-memory-server apollo-server-testing
在项目根目录新建jest.config.js,基础配置如下:

/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: ['**/**/*.test.ts'],
  verbose: true,
  forceExit: true,
  clearMocks: true,
  resetMocks: true,
  restoreMocks: true,
}

在package.json中添加测试脚本:

{
  "scripts": {
    "test": "jest"
  }
}

TypeGoose Schema 单元测试写法

Schema测试核心覆盖三类逻辑:字段约束、中间件钩子、自定义方法,测试时用内存MongoDB替代真实数据库,避免污染业务数据。
以项目里的Admin Schema为例,测试文件admin.schema.test.ts示例:

import { MongoMemoryServer } from 'mongodb-memory-server'
import mongoose from 'mongoose'
import bcrypt from 'bcrypt'
import { AdminModel } from './admin.schema'

let mongo: MongoMemoryServer
// 测试前启动内存Mongo、建立连接
beforeAll(async () => {
  mongo = await MongoMemoryServer.create()
  const uri = mongo.getUri()
  await mongoose.connect(uri)
})
// 每个用例跑完清空Admin集合
beforeEach(async () => {
  await AdminModel.deleteMany({})
  jest.clearAllMocks()
})
// 所有用例跑完断开连接、停掉内存服务
afterAll(async () => {
  await mongoose.connection.close()
  await mongo.stop()
})

describe('Admin Schema测试', () => {
  it('保存文档时自动哈希明文密码', async () => {
    const plainPwd = 'test123456'
    const admin = await AdminModel.create({
      firstname: 'test',
      email: 'test@admin.com',
      password: plainPwd
    })
    // 校验存到库的密码不是明文
    expect(admin.password).not.toBe(plainPwd)
    // 校验哈希值可以和明文匹配
    const isMatch = await bcrypt.compare(plainPwd, admin.password)
    expect(isMatch).toBe(true)
  })

  it('email字段加了唯一约束,重复邮箱保存会报错', async () => {
    await AdminModel.create({
      firstname: 'test1',
      email: 'repeat@admin.com',
      password: '123456'
    })
    await expect(
      AdminModel.create({
        firstname: 'test2',
        email: 'repeat@admin.com',
        password: '123456'
      })
    ).rejects.toThrow()
  })

  it('created字段会自动生成默认值', async () => {
    const admin = await AdminModel.create({
      firstname: 'test',
      email: 'test@admin.com',
      password: '123456'
    })
    expect(admin.created).toBeDefined()
  })
})

TypeGraphQL Resolver 单元测试写法

前置修改提示:当前AdminResolver构造函数内直接实例化AdminService,会导致测试时无法替换依赖做Mock,建议先调整为依赖注入形式,去掉构造函数内的this.adminService = new AdminService(),实例化Resolver时手动传入service实例即可,调整后构造函数如下:

constructor(private adminService: AdminService) {}

Resolver测试分两种粒度,可按需选择:

1. 纯单元测试(速度快,适合逻辑校验)

直接实例化Resolver类,Mock掉所有依赖的Service、上下文对象,直接调用Resolver方法校验入参出参逻辑,示例admin.resolver.test.ts:

import AdminResolver from './admin.resolver'
import AdminService from '../../service/Admin/admin.service'

// Mock整个AdminService
jest.mock('../../service/Admin/admin.service')
const mockAdminService = AdminService as jest.MockedClass<typeof AdminService>

describe('Admin Resolver纯单元测试', () => {
  let resolver: AdminResolver
  beforeEach(() => {
    // 每个用例重新实例化Resolver,传入mock的service
    resolver = new AdminResolver(new mockAdminService())
  })

  it('meAdmin查询返回上下文里的admin信息', () => {
    const mockAdmin = { _id: 'testid', email: 'test@admin.com', firstname: 'test' }
    const mockCtx = { admin: mockAdmin, res: {} }
    const result = resolver.meAdmin(mockCtx as any)
    expect(result).toEqual(mockAdmin)
  })

  it('loginAdmin变更调用service的loginAdmin方法并返回结果', async () => {
    const mockInput = { email: 'test@admin.com', password: '123456' }
    const mockToken = 'test-jwt-token'
    const mockCookieFn = jest.fn()
    const mockCtx = { res: { cookie: mockCookieFn } }
    // mock service方法返回值
    mockAdminService.prototype.loginAdmin.mockResolvedValue(mockToken)

    const result = await resolver.loginAdmin(mockInput, mockCtx as any)
    // 校验service被正确调用
    expect(mockAdminService.prototype.loginAdmin).toHaveBeenCalledWith(mockInput, mockCtx)
    // 校验返回值正确
    expect(result).toBe(mockToken)
  })
})

2. 带GraphQL执行层的测试(更贴近真实请求,适合接口联调校验)

通过apollo-server-testing创建测试用的Apollo Server实例,构造测试Schema后直接发送GraphQL请求,校验完整链路逻辑,示例:

import { ApolloServer, gql } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import AdminResolver from './admin.resolver'
import { buildSchema } from 'type-graphql'

const LOGIN_MUTATION = gql`
  mutation LoginAdmin($input: AdminLoginInput!) {
    loginAdmin(input: $input)
  }
`
const ME_QUERY = gql`
  query MeAdmin {
    meAdmin {
      _id
      email
      firstname
    }
  }
`

describe('Admin Resolver接口测试', () => {
  it('登录接口成功返回token并设置cookie', async () => {
    const mockCookieFn = jest.fn()
    const schema = await buildSchema({
      resolvers: [AdminResolver]
    })
    const server = new ApolloServer({
      schema,
      context: () => ({
        res: { cookie: mockCookieFn },
        admin: null
      })
    })
    const { mutate } = createTestClient(server)
    const res = await mutate({
      query: LOGIN_MUTATION,
      variables: {
        input: { email: 'test@admin.com', password: '123456' }
      }
    })
    expect(res.data?.loginAdmin).toBeDefined()
    expect(mockCookieFn).toHaveBeenCalledWith('accessToken', expect.any(String), expect.objectContaining({
      httpOnly: true,
      sameSite: 'none'
    }))
  })
})

测试注意事项

  • 涉及第三方依赖(比如bcrypt、jwt、nodemailer)的纯单元测试,可以直接Mock对应方法,大幅提升测试执行速度,集成测试场景再走真实逻辑
  • 内存Mongo启动需要等待连接建立完成再执行用例,否则会出现随机连接报错
  • 每个测试用例执行完要清空集合、重置Mock,避免用例之间的数据和状态互相干扰
  • 上下文对象里的req/res属性如果没有被当前测试逻辑用到,可以只Mock用到的字段(比如只用到res.cookie就只实现这个方法),不用构造完整的req/res对象

内容的提问来源于stack exchange,提问作者Danial Bayat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 21:57:22