Jest + ES Modules: “require is not defined” and “extensionsToTreatAsEsm” Errors

Issue with Jest Testing in Express App using ES Modules

I’m currently working on testing my Express app using Jest with ES Modules, but I am encountering a few issues during the test run.

The Problems:

  1. require is not defined — This error occurs when I try to mock my services using jest.mock(). Since I’m using ES modules (type: "module"), I can’t use require, and I’m unsure how to properly mock the services with ES module syntax.

  2. Validation Error:

    Option `extensionsToTreatAsEsm: ['.js']` includes '.js' which is always inferred.
    
    

I added extensionsToTreatAsEsm: [‘.js’] in Jest’s config to ensure .js files are treated as ES modules, but Jest is throwing this validation error, indicating it should be inferred automatically.

My Setup:
Node version: v18.20.4
Jest version: ^29.7.0
ESM support: Enabled with “type”: “module” in my package.json.
Configuration Files:

jest.config.js:

export default {
  transform: {
    '^.+\.js$': 'babel-jest',  // Using babel-jest to handle ES module transformation
  },
  testEnvironment: 'node',  // Set test environment to Node.js
  moduleFileExtensions: ['js', 'mjs'],  // Include ES module extensions
  moduleNameMapper: {
    '^(\.{1,2}/.*)\.js$': '$1',  // Map file extensions correctly for ESM
  },
};

babel.config.js:

export default {
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }],  // Use Babel to transpile ES6+ code for current Node version
  ],
};

Test File (studentController.test.js):

import { jest } from '@jest/globals';
import request from 'supertest'; // Using supertest to test HTTP endpoints
import app from '../server.js'; // Importing the Express server
import { 
  fetchStudentsByTeacherId, 
  fetchStudentById, 
  createNewStudent, 
  updateExistingStudent, 
  processFileUpload 
} from '../services/studentService.js'; // Importing services

// Mock the service layer with ES module syntax
jest.mock('../services/studentService.js', () => ({
  fetchStudentsByTeacherId: jest.fn(),
  fetchStudentById: jest.fn(),
  createNewStudent: jest.fn(),
  updateExistingStudent: jest.fn(),
  processFileUpload: jest.fn(),
}));

describe('Student Controller', () => {
  describe('getStudentsByTeacher', () => {
    it('should fetch students for a teacher by teacher ID', async () => {
      const mockStudents = [{ firstName: 'John', lastName: 'Doe' }];
      fetchStudentsByTeacherId.mockResolvedValue(mockStudents);
      
      const res = await request(app).get('/students/by-teacher/teacher123');
      
      expect(res.status).toBe(200);
      expect(res.body).toEqual(mockStudents);
      expect(fetchStudentsByTeacherId).toHaveBeenCalledWith('teacher123');
    });

    it('should return 500 if an error occurs', async () => {
      fetchStudentsByTeacherId.mockRejectedValue(new Error('Failed to fetch students'));

      const res = await request(app).get('/students/by-teacher/teacher123');

      expect(res.status).toBe(500);
      expect(res.body.message).toBe('Server error');
    });
  });

  describe('getStudentById', () => {
    it('should fetch a student by student ID', async () => {
      const mockStudent = { firstName: 'John', lastName: 'Doe' };
      fetchStudentById.mockResolvedValue(mockStudent);

      const res = await request(app).get('/students/student123');
      
      expect(res.status).toBe(200);
      expect(res.body).toEqual(mockStudent);
      expect(fetchStudentById).toHaveBeenCalledWith('student123');
    });

    it('should return 500 if an error occurs', async () => {
      fetchStudentById.mockRejectedValue(new Error('Failed to fetch student'));

      const res = await request(app).get('/students/student123');

      expect(res.status).toBe(500);
      expect(res.body.message).toBe('Server error');
    });
  });

  describe('addStudent', () => {
    it('should add a new student and return a success message', async () => {
      const mockStudent = { firstName: 'John', lastName: 'Doe' };
      createNewStudent.mockResolvedValue(mockStudent);

      const studentData = {
        firstName: 'John',
        lastName: 'Doe',
        rollNumber: 123,
        class: 'class123',
        guardians: [{ firstName: 'Jane', isPrimaryContact: true }],
        photo: 'url',
      };

      const res = await request(app).post('/students/add').send(studentData);
      
      expect(res.status).toBe(201);
      expect(res.body.message).toBe('Student and guardians added successfully');
      expect(res.body.student).toEqual(mockStudent);
      expect(createNewStudent).toHaveBeenCalledWith(studentData);
    });

    it('should return 500 if an error occurs', async () => {
      createNewStudent.mockRejectedValue(new Error('Failed to add student'));

      const studentData = {
        firstName: 'John',
        lastName: 'Doe',
        rollNumber: 123,
        class: 'class123',
        guardians: [{ firstName: 'Jane', isPrimaryContact: true }],
        photo: 'url',
      };

      const res = await request(app).post('/students/add').send(studentData);
      
      expect(res.status).toBe(500);
      expect(res.body.message).toBe('Error adding student');
    });
  });

  describe('updateStudent', () => {
    it('should update a student and return a success message', async () => {
      const mockUpdatedStudent = { firstName: 'John', lastName: 'Doe', rollNumber: 123 };
      updateExistingStudent.mockResolvedValue(mockUpdatedStudent);

      const studentData = {
        firstName: 'John',
        lastName: 'Doe',
        rollNumber: 123,
        class: 'class123',
        guardians: [{ firstName: 'Jane', isPrimaryContact: true }],
        photo: 'url',
      };

      const res = await request(app).put('/students/edit/student123').send(studentData);
      
      expect(res.status).toBe(200);
      expect(res.body.message).toBe('Student and guardians updated successfully');
      expect(res.body.student).toEqual(mockUpdatedStudent);
      expect(updateExistingStudent).toHaveBeenCalledWith('student123', studentData);
    });

    it('should return 500 if an error occurs', async () => {
      updateExistingStudent.mockRejectedValue(new Error('Failed to update student'));

      const studentData = {
        firstName: 'John',
        lastName: 'Doe',
        rollNumber: 123,
        class: 'class123',
        guardians: [{ firstName: 'Jane', isPrimaryContact: true }],
        photo: 'url',
      };

      const res = await request(app).put('/students/edit/student123').send(studentData);

      expect(res.status).toBe(500);
      expect(res.body.message).toBe('Error updating student');
    });
  });

  describe('uploadStudentFile', () => {
    it('should process the student file upload and return a success message', async () => {
      processFileUpload.mockResolvedValue();

      const res = await request(app).post('/students/upload-student-file').send({ fileUrl: 'https://s3.amazonaws.com/file.csv' });

      expect(res.status).toBe(200);
      expect(res.body.message).toBe('Student file notification received, processing will start shortly.');
      expect(processFileUpload).toHaveBeenCalledWith('https://s3.amazonaws.com/file.csv', 'BULK_IMPORT_STUDENTS');
    });

    it('should return 500 if an error occurs', async () => {
      processFileUpload.mockRejectedValue(new Error('Failed to process file'));

      const res = await request(app).post('/students/upload-student-file').send({ fileUrl: 'https://s3.amazonaws.com/file.csv' });

      expect(res.status).toBe(500);
      expect(res.body.message).toBe('Error processing student file notification');
    });
  });
});

What I’ve Tried:

I’m using Jest with babel-jest to transform ES modules, and I’ve set up Babel to transpile ES6+ for the current Node version.
I included extensionsToTreatAsEsm: [‘.js’] in jest.config.js because I’m working with ES modules (type: “module” in my package.json).

Questions:

How can I mock the services in Jest when using ES modules?
How do I resolve the validation error related to extensionsToTreatAsEsm?