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

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配置未应用到实际被调用的模型上。

解决步骤

  1. 直接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);
    });
    
  2. 调整Mock配置方式
    在测试用例中直接对TrainingProviderDetals模型进行Mock:

    mockingoose(TrainingProviderDetals).toReturn(mockData, "create");
    
  3. 重置Mock状态
    在每个测试用例后重置Mock,避免用例间干扰:

    afterEach(() => {
      mockingoose.resetAll();
    });
    

修改后,create方法会返回你定义的mockData,result.orgId不再为null,即可正常调用toString()方法。


内容的提问来源于stack exchange,提问作者Almighty Dee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 18:50:53