cool-admin(midway版)后端单元测试Mock外部服务与依赖隔离的完整指南【免费下载链接】cool-admin-midway cool-admin(midway版)一个很酷的后台权限管理框架模块化、插件化、CRUD极速开发永久开源免费基于midway.js 3.x、typescript、typeorm、mysql、jwt、vue3、vite、element-ui等构建项目地址: https://gitcode.com/gh_mirrors/co/cool-admin-midwaycool-admin(midway版)是一个基于Midway.js 3.x构建的现代化后台权限管理框架它采用了模块化、插件化的设计理念支持极速CRUD开发。在实际开发中单元测试是确保代码质量的关键环节而Mock外部服务与依赖隔离则是后端单元测试的核心技术。本文将为你详细介绍如何在cool-admin(midway版)中实施高效的单元测试策略。 为什么需要单元测试与Mock技术在cool-admin这样的企业级应用中代码的稳定性和可维护性至关重要。单元测试可以帮助我们快速发现代码缺陷在开发阶段及时发现问题确保重构安全修改代码时不用担心破坏现有功能提高代码质量强制开发者编写可测试的代码文档作用测试用例本身就是最好的使用示例然而后端测试面临的最大挑战就是外部依赖包括数据库、第三方API、文件系统等。这些依赖会使测试变得缓慢且不稳定这时就需要Mock技术来隔离这些外部依赖。 cool-admin的测试环境配置cool-admin项目已经集成了Jest测试框架在package.json中可以看到相关的配置{ devDependencies: { midwayjs/mock: ^3.20.3, types/jest: ^29.5.14, jest: ^29.7.0, ts-jest: ^29.3.0 }, scripts: { test: cross-env NODE_ENVunittest jest, cov: jest --coverage } }项目的jest.config.js配置文件非常简单明了module.exports { preset: ts-jest, testEnvironment: node, testPathIgnorePatterns: [rootDir/test/fixtures], coveragePathIgnorePatterns: [rootDir/test/], }; 核心概念理解cool-admin的服务架构在开始编写测试之前我们需要了解cool-admin的服务架构。项目采用模块化设计每个模块都有独立的Controller处理HTTP请求Service业务逻辑层Entity数据模型层Middleware中间件层以用户管理模块为例服务层代码位于src/modules/base/service/sys/user.ts这是一个典型的依赖注入示例Provide() export class BaseSysUserService extends BaseService { InjectEntityModel(BaseSysUserEntity) baseSysUserEntity: RepositoryBaseSysUserEntity; Inject() baseSysPermsService: BaseSysPermsService; Inject() ctx; }️ Mock实战四种依赖隔离策略1. 数据库依赖的Mock数据库是最常见的外部依赖。在cool-admin中我们可以使用TypeORM的Repository Mockimport { createMock } from midwayjs/mock; import { BaseSysUserService } from ./user; import { Repository } from typeorm; describe(BaseSysUserService, () { let service: BaseSysUserService; beforeEach(async () { // 创建Mock Repository const mockRepository { find: jest.fn().mockResolvedValue([{ id: 1, name: 测试用户 }]), findOne: jest.fn().mockResolvedValue({ id: 1, name: 测试用户 }), save: jest.fn().mockResolvedValue({ id: 1, name: 新用户 }), delete: jest.fn().mockResolvedValue({ affected: 1 }) }; service await createMock(BaseSysUserService); // 替换真实的Repository service.baseSysUserEntity mockRepository as any; }); it(应该返回用户列表, async () { const result await service.page({}); expect(result).toBeDefined(); expect(service.baseSysUserEntity.find).toHaveBeenCalled(); }); });2. 服务间依赖的Mockcool-admin中服务之间经常相互依赖我们可以轻松Mock这些依赖describe(BaseSysUserService with mocked dependencies, () { let service: BaseSysUserService; let mockPermsService: any; beforeEach(async () { mockPermsService { departmentIds: jest.fn().mockResolvedValue([1, 2, 3]) }; service await createMock(BaseSysUserService); service.baseSysPermsService mockPermsService; service.ctx { admin: { userId: 1 } }; }); it(应该根据权限过滤部门, async () { const result await service.page({}); expect(mockPermsService.departmentIds).toHaveBeenCalledWith(1); }); });3. HTTP上下文与请求的Mock对于需要HTTP上下文的测试我们可以Mock ctx对象import { Context } from midwayjs/koa; describe(UserService with mocked context, () { it(应该正确处理用户请求, async () { const mockCtx { admin: { userId: 1, username: admin, roleIds: [1] }, request: { headers: { x-forwarded-for: 127.0.0.1 } } }; const service await createMock(BaseSysUserService); service.ctx mockCtx; // 测试依赖ctx的方法 const ip await service.getReqIP(mockCtx as any); expect(ip).toBe(127.0.0.1); }); });4. 缓存服务的Mockcool-admin使用Midway.js的缓存服务测试时需要Mockimport { CachingFactory, MidwayCache } from midwayjs/cache-manager; describe(Cache dependent service, () { let service: any; let mockCache: jest.MockedMidwayCache; beforeEach(async () { mockCache { get: jest.fn(), set: jest.fn(), del: jest.fn(), reset: jest.fn() }; service await createMock(SomeCacheService); service.midwayCache mockCache; }); it(应该使用缓存获取数据, async () { mockCache.get.mockResolvedValue(cached_value); const result await service.getCachedData(key); expect(result).toBe(cached_value); expect(mockCache.get).toHaveBeenCalledWith(key); }); }); 测试覆盖率与最佳实践测试覆盖率的重要性运行npm run cov命令可以生成测试覆盖率报告这有助于识别未测试的代码路径确保关键业务逻辑被覆盖提高代码质量信心cool-admin测试最佳实践隔离测试环境使用NODE_ENVunittest确保测试不会影响生产数据Mock外部HTTP调用使用jest.mock或nock来模拟API调用测试边界条件包括异常情况、空数据、错误输入等保持测试独立每个测试用例应该相互独立不依赖执行顺序使用描述性的测试名称让测试用例本身成为文档实用工具函数测试示例查看src/comm/utils.ts中的工具函数我们可以这样测试import { Utils } from ./utils; describe(Utils, () { let utils: Utils; beforeEach(() { utils new Utils(); }); describe(sleep方法, () { it(应该等待指定时间, async () { const start Date.now(); await utils.sleep(100); const end Date.now(); expect(end - start).toBeGreaterThanOrEqual(100); }); }); describe(removeEmptyP方法, () { it(应该移除空值属性, async () { const obj { a: value, b: null, c: , d: undefined }; await utils.removeEmptyP(obj); expect(obj).toEqual({ a: value }); }); }); }); 实战为cool-admin模块编写完整测试套件让我们以为用户服务编写完整测试为例import { createMock } from midwayjs/mock; import { BaseSysUserService } from ../../modules/base/service/sys/user; import { CoolCommException } from cool-midway/core; describe(BaseSysUserService 完整测试套件, () { let userService: BaseSysUserService; let mockRepo: any; let mockPermsService: any; let mockCache: any; beforeEach(async () { // 准备所有Mock对象 mockRepo { find: jest.fn(), findOne: jest.fn(), save: jest.fn(), delete: jest.fn(), createQueryBuilder: jest.fn(() ({ leftJoinAndSelect: jest.fn().mockReturnThis(), where: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), getMany: jest.fn(), getOne: jest.fn(), getCount: jest.fn() })) }; mockPermsService { departmentIds: jest.fn().mockResolvedValue([1, 2, 3]) }; mockCache { get: jest.fn(), set: jest.fn() }; // 创建服务实例并注入Mock依赖 userService await createMock(BaseSysUserService); userService.baseSysUserEntity mockRepo; userService.baseSysPermsService mockPermsService; userService.midwayCache mockCache; userService.ctx { admin: { userId: 1 } }; }); describe(分页查询, () { it(应该返回分页数据, async () { mockRepo.createQueryBuilder().getCount.mockResolvedValue(10); mockRepo.createQueryBuilder().getMany.mockResolvedValue([ { id: 1, name: 用户1 }, { id: 2, name: 用户2 } ]); const result await userService.page({ page: 1, size: 10 }); expect(result).toHaveProperty(list); expect(result).toHaveProperty(pagination); }); it(应该根据关键词过滤, async () { const queryBuilder mockRepo.createQueryBuilder(); await userService.page({ keyWord: 测试 }); expect(queryBuilder.andWhere).toHaveBeenCalledWith( a.name LIKE :keyWord OR a.username LIKE :keyWord, { keyWord: %测试% } ); }); }); describe(用户管理, () { it(应该成功添加用户, async () { const userData { username: test, password: 123456 }; mockRepo.save.mockResolvedValue({ id: 1, ...userData }); const result await userService.add(userData); expect(result.id).toBe(1); expect(mockRepo.save).toHaveBeenCalled(); }); it(应该验证用户名重复, async () { mockRepo.findOne.mockResolvedValue({ id: 1, username: existing }); await expect(userService.add({ username: existing })) .rejects.toThrow(CoolCommException); }); }); }); 测试策略与持续集成分层测试策略单元测试测试单个函数或方法本文重点集成测试测试模块间的协作E2E测试测试完整的业务流程持续集成配置建议在GitHub Actions或GitLab CI中配置name: Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv2 - uses: actions/setup-nodev2 with: node-version: 18 - run: npm ci - run: npm test - run: npm run cov 总结通过本文的详细指南你已经掌握了在cool-admin(midway版)中实施后端单元测试的关键技术。Mock外部服务与依赖隔离不仅能提高测试速度还能让测试更加稳定可靠。记住测试驱动开发先写测试再写实现代码全面覆盖不仅要测试正常流程还要测试异常情况持续改进定期审查和优化测试代码cool-admin的模块化架构为测试提供了良好的基础结合Midway.js的强大依赖注入和Jest的Mock功能你可以轻松构建高质量、可维护的测试套件。开始为你的cool-admin项目编写测试吧这将是你代码质量的重要保障测试是代码质量的守护者好的测试能让你的项目更加健壮可靠【免费下载链接】cool-admin-midway cool-admin(midway版)一个很酷的后台权限管理框架模块化、插件化、CRUD极速开发永久开源免费基于midway.js 3.x、typescript、typeorm、mysql、jwt、vue3、vite、element-ui等构建项目地址: https://gitcode.com/gh_mirrors/co/cool-admin-midway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考