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

NestJS控制器测试Mock服务未生效:为何仍调用原RoomService?

问题

刚接触NestJS,正在做控制器测试。RoomController依赖RoomService,RoomService依赖Mongoose的RoomModel,而RoomModel引入了其他模块的实体。测试时已经通过useValue注入Mock的RoomService,但运行测试报错:

Cannot find module 'src/modules/building/models/building.model' from 'modules/room/models/room.model.ts'

看起来测试仍在加载原RoomService相关的依赖,试了多种Mock注入方式都没用,求问题原因。


相关代码

RoomController代码

@Controller('rooms')
export class RoomController {
  constructor(private readonly roomService: RoomService) {}

  @Get()
  async findAll(): Promise<ReadRoomDto[]> {
    return await this.roomService.findAll();
  }
}

RoomService代码

@Injectable()
export class RoomService {
  constructor(
    @InjectModel(Room.name) private readonly roomModel: RoomModel,
  ) {}

  async findAll():Promise<ReadRoomDto[]> {
    return await this.roomModel.find();
  }
}

RoomModel代码

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Model, Types } from 'mongoose';
import { Building } from 'src/modules/building/models/building.model';
import { Floor } from 'src/modules/floor/entities/floor.entity';
import { Organization } from 'src/modules/organization/models/organization.model';
import { Property } from 'src/modules/property/models/property.model';

interface RoomAttrs {
  name: string;
  floor?: string;
  building?: string;
  organization?: string;
  properties?: string[];
}

export interface RoomModel extends Model<Room> {
  build(attrs: RoomAttrs): Room;
}

@Schema()
export class Room extends Document {
  @Prop()
  name: String;

  @Prop({ type: String, required: false, ref: Floor.name })
  floor: String;

  @Prop({ type: String, required: false, ref: Building.name })
  building: String;

  @Prop({ type: String, required: false, ref: Organization.name })
  organization: String;

  @Prop([{ type: String, required: false, ref: Property.name }])
  properties: String[];
}

export const RoomSchema = SchemaFactory.createForClass(Room);

RoomSchema.set('toJSON', {
  transform: (doc, ret) => {
    ret.id = doc._id;
    delete ret._id;
  },
});

RoomSchema.statics.build = function (attrs: RoomAttrs) {
  return new this(attrs);
};

测试代码

import { Test, TestingModule } from '@nestjs/testing';
import { RoomController } from '../controllers/room.controller';
import { RoomStub } from './stubs/room.stub';
import { RoomService } from '../services/room.service';

const mockRoomService = {
  findAll: jest.fn().mockResolvedValue([RoomStub()]),
};

describe('RoomController', () => {
  let roomController: RoomController;
  //let roomService :RoomService

  beforeAll(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [RoomController],
      providers: [{provide:RoomService,useValue:mockRoomService}],
      
    }).compile();
    roomController = module.get<RoomController>(RoomController);
    //roomService = module.get<RoomService>(RoomService)
  });

  it('should be defined', () => {
    expect(roomController).toBeDefined();
  });
});

解决方案

问题根源

你遇到的不是Mock注入失效,而是TypeScript模块路径解析错误,加上测试时依赖加载的连锁反应:

  1. RoomModel里用了src/...开头的绝对导入路径,但测试运行时,从modules/room/models/room.model.ts的位置无法正确解析src/开头的路径;
  2. 虽然你Mock了RoomService,但测试代码里import了原RoomService文件,TypeScript会加载它的依赖链(RoomService→RoomModel→其他模块实体),触发了错误的路径解析。

解决步骤

1. 修复模块导入路径

两种方式二选一:

  • 改用相对路径:根据实际目录结构,把RoomModel中的绝对导入改成相对路径,比如:
    // 原路径
    import { Building } from 'src/modules/building/models/building.model';
    // 改为相对路径(示例,根据你的目录层级调整)
    import { Building } from '../../building/models/building.model';
    
  • 配置路径别名:在tsconfig.json中添加路径映射,确保绝对路径能被正确解析:
    {
      "compilerOptions": {
        "baseUrl": "./",
        "paths": {
          "src/*": ["src/*"]
        }
      }
    }
    
    同时在jest.config.js中同步配置Jest的路径映射:
    module.exports = {
      // 其他配置项
      moduleNameMapper: {
        '^src/(.*)$': '<rootDir>/src/$1',
      },
    };
    

2. 彻底隔离依赖(可选)

如果不想加载原RoomService的任何依赖,可以用jest.mock直接Mock整个RoomService模块,代替测试模块中的useValue注入:

import { Test, TestingModule } from '@nestjs/testing';
import { RoomController } from '../controllers/room.controller';
import { RoomStub } from './stubs/room.stub';
import { RoomService } from '../services/room.service';

// 直接Mock整个RoomService模块
jest.mock('../services/room.service', () => ({
  RoomService: jest.fn().mockImplementation(() => ({
    findAll: jest.fn().mockResolvedValue([RoomStub()]),
  })),
}));

describe('RoomController', () => {
  let roomController: RoomController;

  beforeAll(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [RoomController],
      providers: [RoomService], // 直接使用Mock后的服务
    }).compile();
    roomController = module.get<RoomController>(RoomController);
  });

  it('should be defined', () => {
    expect(roomController).toBeDefined();
  });
});

内容的提问来源于stack exchange,提问作者simple-User

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 10:53:29