I’m fairly new to the Jest testing framework and am writing unit tests for a user registration endpoint that handles image uploads using GridFS. I’m getting the following error when running my tests:
Failed to upload image: Cannot read properties of undefined (reading ‘openUploadStream’)
RegisterUser.test.mjs file
import { RegisterUser } from "../../controllers/RegisterUser.mjs";
import { jest } from '@jest/globals'
// jest.mock('mongoose', () => ({
// once: jest.fn((event, callback) => {
// if (event === 'open') {
// callback();
// }
// }),
// db: {}
// }));
// jest.mock('mongodb', () => ({
// GridFSBucket: jest.fn().mockImplementation(() => ({
// openUploadStream: jest.fn().mockReturnValue({
// id: 'mockFileId',
// on: jest.fn((event, callback) => {
// if (event === 'finish') {
// callback();
// } else if (event === 'error') {
// callback(new Error("Mocked error"));
// }
// }),
// })
// }))
// }));
// jest.mock('stream', () => ({
// Readable: jest.fn(() => ({
// push: jest.fn(),
// }))
// }));
const mockUploadImage = jest.fn();
// Mock the entire module with the tracked function
jest.mock('../../config/gridfs-setup.mjs', () => ({
uploadImage: mockUploadImage,
gfs: {
openUploadStream: jest.fn()
}
}));
describe('Profile Image Upload', () => {
it("should handle image upload and save it to the database", async () => {
let mockRequest = {
body: {
email: "[email protected]",
password: "Password?123",
username: 'testuser',
mobileNumber: "+6582183334",
image: 'data:image/png;base64,abc123',
}
}
let mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
const base64Image = mockRequest.body.image;
const username = mockRequest.body.username;
const imageBuffer = Buffer.from(base64Image.replace(/^data:image/w+;base64,/, ''), 'base64');
jest.spyOn(Date, 'now').mockImplementation(() => new Date('2025-01-01'));
const imageName = `profile-${Date.now()} - ${username}`;
await RegisterUser(mockRequest, mockResponse);
expect(mockUploadImage).toHaveBeenCalled();
})
})
gridfs-setup.mjs file
import mongoose from "mongoose";
import { GridFSBucket } from 'mongodb';
import { Readable } from 'stream';
const connection = mongoose.connection;
let gfs;
connection.once('open', () => {
gfs = new GridFSBucket(connection.db, {
bucketName: "images" // Creating a special database for images
});
});
const uploadImage = async (imageName, imageBuffer, metadata = {}) => {
try {
// Creates a writable stream that we can use to write our image data into GridFS
const uploadStream = gfs.openUploadStream(imageName, {
metadata: {
...metadata,
uploadDate: new Date()
}
});
// Create a readable stream for us to read the data and connect it with uploadStream (writable)
const bufferStream = new Readable();
bufferStream.push(imageBuffer);
bufferStream.push(null); // Signals we are are done sending the data
// When everything is safely stored, we return the storage location or the error
return new Promise((resolve, reject) => {
bufferStream.pipe(uploadStream) // Connects the bufferStream(readable) to the uploadStream(writable)
.on('finish', () => resolve(uploadStream.id))
.on('error', reject);
});
} catch (error) {
throw new Error(`Failed to upload image: ${error.message}`);
}
};
export { gfs, uploadImage };
RegisterUser.mjs file (controller)
import User from "../models/User.mjs";
import OTP from "../models/OTPSchema.mjs";
import bcrypt from "bcrypt";
import { uploadImage } from "../config/gridfs-setup.mjs";
import sendEmail from "./SendOTP.mjs";
export const RegisterUser = async (req, res) => {
// try {
const { email, password, username, mobileNumber, image, googleId } = req.body;
// Submitted image data comes as a base64 string and we need to convert it to binary data
// for computers to handle.
const imageBuffer = Buffer.from(
image.replace(/^data:image/w+;base64,/, ''),
'base64'
);
// Create a unique name for the image file
const imageName = `profile-${Date.now()} - ${username}`
// Send the image to GridFS storage system and get back an id
const fileId = await uploadImage(imageName, imageBuffer, {
type: 'profile',
username: username
});
// const newUser = new User({
// googleId,
// email,
// password,
// username,
// mobileNumber,
// profileImage: fileId,
// authProvider: 'local',
// });
// req.session.pendingUser = newUser;
// const otp = Math.floor(1000 + Math.random() * 9000).toString();
// await OTP.create({
// email,
// otp: await bcrypt.hash(otp, 10)
// });
// const subject = "Registration Email Verification";
// const message = `Please use the following One Time Password: ${otp}. This OTP will expire in 5minutes.`
// try {
// await sendEmail({
// recipient: email,
// subject: subject,
// message: message
// });
// } catch (error) {
// console.error('Email sending failed:', error);
// }
res.status(201).json({
success: true,
redirectTo: `/authentication/verify/${newUser._id}`,
});
// } catch (error) {
// if (error.code === 11000) {
// // The key is essential for attaching the error message to the corresponding
// // error object in the react hook form (client)
// const keys = Object.keys(error.keyPattern);
// const key = keys[0];
// if (error.keyValue.email) {
// return res.status(400).json({
// success: false,
// message: "Email address is already in use",
// key: key,
// })
// } else if (error.keyValue.username) {
// return res.status(400).json({
// success: false,
// message: "Username is taken",
// key: key,
// })
// } else if (error.keyValue.mobileNumber) {
// return res.status(400).json({
// success: false,
// message: "Mobile number is already in use",
// key: key,
// })
// }
// }
// res.status(500).json({
// success: false,
// message: "There is something wrong with the server. Please try again later."
// })
// }
}
I have attempted to mock both the Mongoose and MongoDB modules, but I’m still encountering the same error.