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

Jest测试报错:TypeError: Article is not a constructor 求助

问题

为article.service.js中的saveArticle函数编写Jest单元测试时,触发错误:TypeError: Article is not a constructor,无法定位原因。

项目中article.model.js基于Mongoose Schema定义了Article模型并导出,article.service.js通过解构引入后用new Article()创建实例。测试文件尝试mock Article模型,但执行测试时报错。测试目标是验证slugify和currentUser函数是否按预期调用。

相关代码如下:

article.model.js

const mongoose = require('mongoose');

const Schema = mongoose.Schema;

const articleSchema = new Schema(
  {
    slug: { type: String },
    title: { type: String, required: true },
    description: { type: String, required: true },
    body: { type: String, required: true },
    tagList: [String],
    favoritesCount: { type: Number, default: 0 },
    comments: [{ type: mongoose.Types.ObjectId, ref: 'Comment' }],
    author: { type: mongoose.Types.ObjectId, ref: 'User' },
  },
  { timestamps: true }
);

const Article = mongoose.model('Article', articleSchema);
module.exports.Article = Article;

article.service.js

const { Article } = require('../models/article.model');
const { currentUser } = require('../services/user.service');
const { slugify } = require('../helpers/slugify.helper');

class ArticleService {
  static async saveArticle(userId, articleObj) {
    const newArticle = new Article();

    // ...more code here...
  }
}

article.service.test.js

const slugifyHelper = require('../../../helpers/slugify.helper');
const currentUserService = require('../../../services/user.service');
const { Article } = require('../../../models/article.model');

const mockUser = {
  _id: "6368ad4347dkhs",
  email: "test@gmail.com"
};

const mockArticle = {
  title: "Sample article title",
  description: "Sample article description",
  body: "Sample article body",
  tagList: ['tag1', 'tag2']
};

// Mock the Article model
jest.mock('../../../models/article.model', () => ({
  save: jest.fn().mockResolvedValue(mockUser)
}));

const slugifySpy = jest.spyOn(slugifyHelper, "slugify").mockReturnValue('mocked-slug');
const currentUserSpy = jest.spyOn(currentUserService, "currentUser").mockResolvedValue(mockUser);

describe("Unit tests for article.service.js file", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });
  describe("saveArticle tests", () => {
    test("If user can save an article", async () => {
      //Arrange
      const { saveArticle } = require("../../../services/article.service");

      //Act
      const result = await saveArticle(mockUser._id, mockArticle);

      //Assert
      expect(Article).toHaveBeenCalledTimes(1);
      expect(slugifySpy).toHaveBeenCalledTimes(1);
      expect(slugifySpy).toHaveBeenCalledWith(mockArticle.title);
      expect(currentUserSpy).toHaveBeenCalledTimes(1);
      expect(currentUserSpy).toHaveBeenCalledWith(mockUser._id);
    });
  });
});

解决方案

错误原因

你在jest.mock中模拟的模块返回值结构错误:原模块导出的是包含Article构造函数的对象,但你的mock返回的是带save方法的普通对象,没有提供Article构造函数,导致new Article()调用失败。

修复步骤

  1. 修正mock的模块结构
    调整jest.mock返回值,匹配原模块的导出结构,把Article模拟成可实例化的构造函数,并为实例添加save方法:

    jest.mock('../../../models/article.model', () => ({
      Article: jest.fn().mockImplementation(() => ({
        save: jest.fn().mockResolvedValue({ ...mockArticle, _id: 'mock-id', slug: 'mocked-slug' })
      }))
    }));
    
  2. 调整断言逻辑(可选)
    若需要验证实例的save方法调用,可添加以下断言:

    const mockArticleInstance = Article.mock.results[0].value;
    expect(mockArticleInstance.save).toHaveBeenCalledTimes(1);
    
  3. 优化模块引入时机
    建议先完成mock,再引入目标服务和依赖,避免模块提前加载导致mock失效:

    // 先执行mock
    jest.mock('../../../models/article.model', () => ({
      Article: jest.fn().mockImplementation(() => ({
        save: jest.fn().mockResolvedValue({ ...mockArticle, _id: 'mock-id', slug: 'mocked-slug' })
      }))
    }));
    
    // 再引入相关模块
    const slugifyHelper = require('../../../helpers/slugify.helper');
    const currentUserService = require('../../../services/user.service');
    const { Article } = require('../../../models/article.model');
    const { saveArticle } = require("../../../services/article.service");
    

完整修复后的测试文件示例

const slugifyHelper = require('../../../helpers/slugify.helper');
const currentUserService = require('../../../services/user.service');

const mockUser = {
  _id: "6368ad4347dkhs",
  email: "test@gmail.com"
};

const mockArticle = {
  title: "Sample article title",
  description: "Sample article description",
  body: "Sample article body",
  tagList: ['tag1', 'tag2']
};

// 修正后的mock
jest.mock('../../../models/article.model', () => ({
  Article: jest.fn().mockImplementation(() => ({
    save: jest.fn().mockResolvedValue({ ...mockArticle, _id: 'mock-article-id', slug: 'mocked-slug', author: mockUser._id })
  }))
}));

// 引入mock后的模块和服务
const { Article } = require('../../../models/article.model');
const { saveArticle } = require("../../../services/article.service");

const slugifySpy = jest.spyOn(slugifyHelper, "slugify").mockReturnValue('mocked-slug');
const currentUserSpy = jest.spyOn(currentUserService, "currentUser").mockResolvedValue(mockUser);

describe("Unit tests for article.service.js file", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });
  describe("saveArticle tests", () => {
    test("If user can save an article", async () => {
      //Act
      const result = await saveArticle(mockUser._id, mockArticle);

      //Assert
      expect(Article).toHaveBeenCalledTimes(1);
      // 断言实例的save方法被调用
      const articleInstance = Article.mock.results[0].value;
      expect(articleInstance.save).toHaveBeenCalledTimes(1);
      
      expect(slugifySpy).toHaveBeenCalledTimes(1);
      expect(slugifySpy).toHaveBeenCalledWith(mockArticle.title);
      expect(currentUserSpy).toHaveBeenCalledTimes(1);
      expect(currentUserSpy).toHaveBeenCalledWith(mockUser._id);
    });
  });
});

内容的提问来源于stack exchange,提问作者Nilesh Sanyal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 12:57:38