如何在Node.js项目中用Mocha+Sinon测试控制器层(含tsyringe)
Mocha测试tsyringe依赖注入控制器的Stub方案
核心逻辑
tsyringe靠容器管理依赖,测试时不用修改原代码,直接把容器里的真实服务替换成Stub即可,这样控制器拿到的就是你构造的假服务,完全隔离真实业务逻辑。
一步步实现(附代码示例)
先看基础项目结构
假设你的控制器和服务是这样写的:
// src/services/UserService.ts import { injectable } from 'tsyringe'; @injectable() export class UserService { async getUserById(id: string) { // 实际数据库查询逻辑 return { id, name: 'John Doe' }; } } // src/controllers/UserController.ts import { inject, injectable } from 'tsyringe'; import { Request, Response } from 'express'; import { UserService } from '../services/UserService'; @injectable() export class UserController { constructor(@inject(UserService) private userService: UserService) {} async getUser(req: Request, res: Response) { const user = await this.userService.getUserById(req.params.id); res.json(user); } }
测试前必须重置容器
每次测试前清空容器内的实例,避免上一个测试的Stub残留干扰后续测试:
import { container } from 'tsyringe'; beforeEach(() => { container.clearInstances(); // 清空已创建的依赖实例 });
用Sinon创建Stub并替换容器服务
这里用Sinon做示例,用Jest Mock也可以,逻辑完全一致:
import { expect } from 'chai'; import sinon from 'sinon'; import { container } from 'tsyringe'; import { UserController } from '../src/controllers/UserController'; import { UserService } from '../src/services/UserService'; import { Request, Response } from 'express'; describe('UserController', () => { beforeEach(() => { container.clearInstances(); }); it('调用getUser应返回Mock的用户数据', async () => { // 1. 构造UserService的Stub,指定getUserById的返回值 const userServiceStub = { getUserById: sinon.stub().resolves({ id: '123', name: 'Mocked Jane' }) }; // 2. 将容器中的UserService替换为Stub container.register(UserService, { useValue: userServiceStub }); // 3. 从容器获取控制器实例——这步必须做,不能直接new,否则依赖不会替换 const controller = container.resolve(UserController); // 4. 模拟Request和Response对象 const mockReq = { params: { id: '123' } } as Request; const mockRes = { json: sinon.spy() } as unknown as Response; // 5. 执行控制器方法 await controller.getUser(mockReq, mockRes); // 6. 验证结果是否符合预期 expect(userServiceStub.getUserById.calledWith('123')).to.be.true; expect(mockRes.json.calledWith({ id: '123', name: 'Mocked Jane' })).to.be.true; }); });
常见问题排查
- Stub不生效:别直接
new UserController(),一定要用container.resolve()获取实例,否则控制器会依赖真实服务而非Stub。 - 测试状态污染:务必在
beforeEach或afterEach中调用container.clearInstances(),清除上一个测试的残留实例。 - TypeScript类型报错:可以用
sinon.createStubInstance(UserService)自动生成与原服务类型匹配的Stub,省去手动定义结构的麻烦:
const userServiceStub = sinon.createStubInstance(UserService); userServiceStub.getUserById.resolves({ id: '123', name: 'Mocked Jane' });
总结
核心就是利用tsyringe的容器替换机制,测试前将真实服务替换为Stub,再从容器中获取控制器实例,这样控制器的依赖就会指向Stub,从而实现控制器逻辑的独立单元测试。
内容的提问来源于stack exchange,提问作者Thiago Henrique Cunha
相关产品推荐
相关产品推荐

