Mockingoose在Jest测试中未返回模拟数据问题求助
问题:Mockingoose未返回指定模拟值,导致TypeError: Cannot read properties of null (reading 'toString')
我正在用Jest和Mockingoose编写测试,因需遵循现有代码库模式,未使用Jest的spyOn。但Mock模型后未返回指定模拟值,持续报错TypeError: Cannot read properties of null (reading 'toString')。我认为配置与代码库一致,却未达预期效果,该如何解决?
错误信息
> jest --config ./configs/jest.config.js --color --verbose --no-cache training-provider.service.spec.ts console.log response { formBuilderId: new ObjectId("6523c6c939e8b4eb9afb0d79"), userId: null, formData: { feinInfo: { federalNumber: '17994231', companyName: 'Test Company Name', companyWebsite: '', companyType: 'academicInstitutionsWithABusinessSupportProgram', primaryLocation: [Object], billingLocation: [Object] }, basicInfo: { title: 'Job Title', firstName: 'John', lastName: 'Doe', pronouns: '', phonenumber: '1299995231', email: 'johndoe@gmail.com' } }, modifiedBy: null, orgId: null, isVerified: false, userStatus: 6, verifiedDate: null, verifiedBy: null, _id: new ObjectId("68620543914dccbe5da29013"), createdAt: 2025-06-30T03:32:19.305Z, modifiedAt: 2025-06-30T03:32:19.305Z, program_services: [], support_services: [], education_training_provider: [] } at TrainingProviderService.test (src/training-provider/training-provider.service.ts:22:13) FAIL src/training-provider/training-provider.service.spec.ts (8.163 s) TrainingProviderService √ should be defined (11 ms) test method × should be defined (20 ms) ● TrainingProviderService › test method › should be defined TypeError: Cannot read properties of null (reading 'toString') 135 | 136 | expect(result).toBeDefined(); > 137 | expect(result.orgId.toString()).toBe(MOCK_CREATED_ORG._id.toString()); | ^ 138 | }); 139 | }); 140 | }); at Object.<anonymous> (src/training-provider/training-provider.service.spec.ts:137:27) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 passed, 2 total
Schema 定义
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose' import { ApiProperty } from '@nestjs/swagger' import { Schema as MongoSchema } from 'mongoose' import { TrainingProviderStatus } from '../enums' export type TrainingProviderDocument = TrainingProviderDetals & Document @Schema({ collection: 'training_provider_details' }) export class TrainingProviderDetals { @ApiProperty({ example: 'Assessment form', description: 'This is the form component type', }) @Prop({ required: true }) formBuilderId: MongoSchema.Types.ObjectId @ApiProperty({ example: '594ced02ed345b2b049222c5', description: 'This form is belong to which tenant', }) @Prop({ type: MongoSchema.Types.ObjectId, ref: 'User', default: null }) userId: MongoSchema.Types.ObjectId @ApiProperty({ example: { display: 'form', components: [] }, description: 'This is the JSON schema form', }) @Prop({ type: Object, default: {} }) formData: unknown @Prop({ type: Object, required: false }) applicationRejectionInfo: Object @ApiProperty({ example: '594ced02ed345b2b049222c1', description: 'Who created the form', }) @Prop() createdBy: MongoSchema.Types.ObjectId @ApiProperty({ example: '2023-04-12T14:45:44.568+00:00', description: 'When its created', }) @Prop({ default: Date.now }) createdAt: Date @ApiProperty({ example: '594ced02ed345b2b049222c1', description: 'Who created the form', }) @Prop({ type: MongoSchema.Types.ObjectId, ref: 'User', default: null }) modifiedBy: MongoSchema.Types.ObjectId @Prop({ default: Date.now }) modifiedAt: Date @Prop({ type: MongoSchema.Types.ObjectId, ref: 'Organization', default: null, }) orgId: MongoSchema.Types.ObjectId @Prop({ type: String }) searchIndexData: string @Prop({ type: String }) profilepic: string @Prop({ type: String }) reason: string @Prop({ type: Boolean, default: false }) isVerified: boolean @Prop({ type: Array, default: [] }) program_services: [] @Prop({ type: Array, default: [] }) support_services: [] @Prop({ type: Array, default: [] }) education_training_provider: [] @Prop({ default: TrainingProviderStatus.RegisteredUser, type: Number }) userStatus: number @Prop({ default: '', type: Date }) verifiedDate: Date @Prop({ type: MongoSchema.Types.ObjectId, ref: 'User', default: null }) verifiedBy: Date @Prop() lastEtplNotfication: Date @Prop() lastStatusUpdate: Date @Prop() lastApplicationReviewNotification: Date } export const TrainingProviderDetalsSchema = SchemaFactory.createForClass( TrainingProviderDetals, ) TrainingProviderDetalsSchema.index({ searchIndexData: 'text' })
Model 定义
import { Injectable } from '@nestjs/common' import { InjectModel } from '@nestjs/mongoose' import { Model } from 'mongoose' import { TrainingProviderDetals } from '../schemas/training-provider.schema' /** * @author Mantu * @description This model is used to history */ @Injectable() export class TrainingProviderDetailModel { constructor( @InjectModel(TrainingProviderDetals.name) private readonly trainingProviderDetails: Model<TrainingProviderDetals>, ) {} async create(data: any): Promise<TrainingProviderDetals> { return await this.trainingProviderDetails.create(data) } }
Service 代码
import { AssessmentUtilities } from '@app/assessment/utilities'; import { EncryptDecrypt } from '@app/encryption'; import { Injectable, Logger } from '@nestjs/common'; import { CreateTrainingProviderDto, UpdateTrainingProviderDto, } from './dtos/create-training-provider.dto'; import { TrainingProviderDetailModel } from './models/training-provider.model'; @Injectable() export class TrainingProviderService { utilities = null; encryptDecrypt = null; logger = new Logger(TrainingProviderService.name); constructor( private readonly trainingModel: TrainingProviderDetailModel, ) { this.utilities = new AssessmentUtilities(); this.encryptDecrypt = new EncryptDecrypt(); } async test(body: CreateTrainingProviderDto | UpdateTrainingProviderDto) { let response = await this.trainingModel.create(body); console.log("response", response); return response; } }
测试代码
process.env.CSFLE_ENCRYPTION_KEY = Buffer.from('a'.repeat(32)).toString( 'base64', ); process.env.CSFLE_SIGNING_KEY = Buffer.from('b'.repeat(64)).toString('base64'); import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; import mongoose, { Model, Types } from 'mongoose'; import { TrainingProviderDetailModel } from './models/training-provider.model'; import { TrainingProviderDetals, TrainingProviderDetalsSchema, } from './schemas/training-provider.schema'; import { TrainingProviderService } from './training-provider.service'; const mockingoose = require('mockingoose'); describe('TrainingProviderService', () => { const MOCK_PROVIDER_REGISTRATION = { formBuilderId: '6523c6c939e8b4eb9afb0d79', formData: { grantInfo: {}, feinInfo: { federalNumber: '17994231', companyName: 'Test Company Name', companyWebsite: '', companyType: 'academicInstitutionsWithABusinessSupportProgram', primaryLocation: { country: '', locationName: 'Location Name', address1: 'Address 1', address2: 'Address 2', city: 'City', state: 'Alabama', zipcode: '35004', }, billingLocation: { locationName: 'Location Name', address1: 'Address 1', address2: 'Address 2', city: 'City', state: 'Alabama', zipcode: '35004', }, }, basicInfo: { title: 'Job Title', firstName: 'John', lastName: 'Doe', pronouns: '', phonenumber: '1299995231', email: 'johndoe@gmail.com', address: {}, }, }, }; const now = new Date(); const formattedNow = now.toISOString(); const MOCK_CREATED_ORG = { name: MOCK_PROVIDER_REGISTRATION.formData.feinInfo.companyName, createdBy: new Types.ObjectId(), status: 1, jobAutoApprove: false, disableAutoApproval: false, internshipJobAutoApprove: false, disableInternshipJobAutoApproval: false, _id: new Types.ObjectId('685dcd858d602e2084839bd3'), createdAt: formattedNow, modifiedAt: formattedNow, __v: 0, }; let service: TrainingProviderService; let trainingProviderDetails: Model<TrainingProviderDetals>; beforeEach(async () => { trainingProviderDetails = mongoose.model( TrainingProviderDetals.name, TrainingProviderDetalsSchema, ); const module: TestingModule = await Test.createTestingModule({ providers: [ TrainingProviderService, TrainingProviderDetailModel, { provide: getModelToken(TrainingProviderDetals.name), useValue: trainingProviderDetails, }, ], }).compile(); service = module.get<TrainingProviderService>(TrainingProviderService); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe("test method", () => { it("should be defined", async () => { const mockData = { formBuilderId: new Types.ObjectId( MOCK_PROVIDER_REGISTRATION.formBuilderId ), userId: new Types.ObjectId("685daf125fd65ffc922ef8ee"), formData: MOCK_PROVIDER_REGISTRATION.formData, createdBy: null, createdAt: formattedNow, modifiedBy: null, modifiedAt: formattedNow, orgId: MOCK_CREATED_ORG._id, searchIndexData: ",Location Name,Address 1,Address 2,City,Alabama,35004,Test Company Name,17994231,Job Title,John,Doe,,1299995231,johndoe@gmail.com", isVerified: true, program_services: [], support_services: [], education_training_provider: [ new Types.ObjectId("6555a6153f78ced4f4449faa"), ], userStatus: 5, verifiedDate: null, verifiedBy: null, _id: new Types.ObjectId(), __v: 0, }; mockingoose(trainingProviderDetails).toReturn(mockData, "create"); const result = await service.test(MOCK_PROVIDER_REGISTRATION); expect(result).toBeDefined(); expect(result.orgId.toString()).toBe(MOCK_CREATED_ORG._id.toString()); }); }); });
问题原因及解决方法
原因分析
从日志可见,实际返回的response中orgId为null,但测试配置的mockData里orgId是MOCK_CREATED_ORG._id,说明Mockingoose的模拟未生效。核心问题是:手动创建的模型实例与测试模块注入的模型不是同一个对象——beforeEach中通过mongoose.model()创建的实例,和NestJS注入到TrainingProviderDetailModel的实例不匹配,导致Mock配置未应用到实际被调用的模型上。
解决步骤
直接Mock模型类而非实例
移除手动创建模型的代码,直接使用Mockingoose生成的Mock模型注入测试模块:beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ TrainingProviderService, TrainingProviderDetailModel, { provide: getModelToken(TrainingProviderDetals.name), useValue: mockingoose(TrainingProviderDetals), }, ], }).compile(); service = module.get<TrainingProviderService>(TrainingProviderService); });调整Mock配置方式
在测试用例中直接对TrainingProviderDetals模型进行Mock:mockingoose(TrainingProviderDetals).toReturn(mockData, "create");重置Mock状态
在每个测试用例后重置Mock,避免用例间干扰:afterEach(() => { mockingoose.resetAll(); });
修改后,create方法会返回你定义的mockData,result.orgId不再为null,即可正常调用toString()方法。
内容的提问来源于stack exchange,提问作者Almighty Dee
相关产品推荐
相关产品推荐

