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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 13:25:58