node.jsmultervitest

Mock Multer for multiple tests (vitest and Typescript)


I wanna test my route. The problem is that I can't mock "multer" multiple times with different content. My Test:

import request from "supertest";
import express from "express";
import importRouter from "../routes/import.route"; // Deine Route importieren
import { vi, describe, it, expect, beforeEach } from "vitest";

let counter = 0;

beforeEach(() => {
  // Mocke multer und füge diskStorage statisch hinzu
  vi.mock("multer", () => {
    // Erstelle single und diskStorage innerhalb der Mock-Funktion
    const single = vi.fn(
      (fieldname: any) => (req: any, res: any, next: any) => {
        req.file = {
          originalname: "test.csv",
          size: counter === 2 ? 100 : 600 * 1024, // Simuliere Dateigröße
          path: "uploads/test.csv",
        };

        const error: any =
          counter === 2
            ? new Error("Unknown error")
            : new Error("File too large");
        error.code = counter === 2 ? "UNKNOWN_ERROR" : "LIMIT_FILE_SIZE";
        next(error); // Simuliere Dateigrößenfehler
      }
    );

    const diskStorage = vi.fn(() => ({
      destination: vi.fn((req, file, cb) => cb(null, "uploads")),
      filename: vi.fn((req, file, cb) => cb(null, file.originalname)),
    }));

    const multerMock = () => ({
      single,
      diskStorage: diskStorage(),
    });

    multerMock.diskStorage = diskStorage;

    return {
      default: multerMock,
      diskStorage,
    };
  });

  counter++;
});

describe("POST /import/customers", () => {
  it("should return 400 if file size exceeds limit (512KB)", async () => {
    let app = express();
    app.use(express.json());
    app.use("/import", importRouter);

    // Verwende Supertest, um die Route zu testen
    const res = await request(app)
      .post("/import/customers")
      .attach("file", Buffer.from("Some content"), "test.csv");

    expect(res.status).toBe(400);
    expect(res.body.msg).toBe("File too large. Maximum size is 512KB.");
  });

  it("should return 500 for general file upload error", async () => {
    let app = express();
    app.use(express.json());
    app.use("/import", importRouter);

    const res = await request(app)
      .post("/import/customers")
      .attach("file", Buffer.from("Some content"), "test.csv");

    expect(res.status).toBe(500);
    expect(res.body.msg).toBe("File upload error");
  });
});

And the file:

import { Router } from "express";
import multer from "multer";
import { importCustomers } from "../controllers/import.controller";
import * as path from "path";

const router = Router();
// Multer configuration for file uploads
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    const uploadPath = path.resolve(__dirname, "..", "..", "uploads");
    cb(null, uploadPath);
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname);
  },
});
const upload = multer({
  storage,
  limits: { fileSize: 512 * 1024 }, // 512KB in Bytes
}).single("file");

/**
 * @route POST /import/customers
 * @desc Import customers from a CSV file
 */
router.post(
  "/customers",
  (req, res, next) => {
    upload(req, res, (err) => {
      if (err) {
        if (err.code === "LIMIT_FILE_SIZE") {
          return res
            .status(400)
            .json({ msg: "File too large. Maximum size is 512KB." });
        }
        return res
          .status(500)
          .json({ msg: "File upload error", error: err.message });
      }
      next(); // Go to next controller
    });
  },
  importCustomers
);

export default router;

This test is working as well. But i don't wanna use a "counter" value to handle the different body/content of the multer functions. So how can I do it in a common way?


Solution

  • You can set context dynamically with mock, I believe. NOTE: Haven't tested it, just took something we use in our tests and applied:

    import request from "supertest";
    import express from "express";
    import importRouter from "../routes/import.route";
    import { vi, describe, it, expect, beforeEach } from "vitest";
    
    beforeEach(() => {
      // Reset mocks before each test
      vi.resetModules();
    
      // Mock multer
      vi.mock("multer", () => {
        const single = vi.fn(
          (fieldname: any) => (req: any, res: any, next: any) => {
            req.file = {
              originalname: "test.csv",
              size: 600 * 1024, // Default size
              path: "uploads/test.csv",
            };
    
            // Check if a custom error is set on the request object for testing purposes
            if (req.mockError) {
              const error: any = new Error(req.mockError.message);
              error.code = req.mockError.code;
              return next(error);
            }
    
            next();
          }
        );
    
        const diskStorage = vi.fn(() => ({
          destination: vi.fn((req, file, cb) => cb(null, "uploads")),
          filename: vi.fn((req, file, cb) => cb(null, file.originalname)),
        }));
    
        const multerMock = () => ({
          single,
          diskStorage: diskStorage(),
        });
    
        multerMock.diskStorage = diskStorage;
    
        return {
          default: multerMock,
          diskStorage,
        };
      });
    });
    
    describe("POST /import/customers", () => {
      it("should return 400 if file size exceeds limit (512KB)", async () => {
        let app = express();
        app.use(express.json());
        app.use("/import", importRouter);
    
        // Mock a file size limit error for this test case
        const res = await request(app)
          .post("/import/customers")
          .attach("file", Buffer.from("Some content"), "test.csv")
          .set("mock-error-code", "LIMIT_FILE_SIZE")
          .set("mock-error-message", "File too large");
    
        expect(res.status).toBe(400);
        expect(res.body.msg).toBe("File too large. Maximum size is 512KB.");
      });
    
      it("should return 500 for general file upload error", async () => {
        let app = express();
        app.use(express.json());
        app.use("/import", importRouter);
    
        // Mock a general error for this test case
        const res = await request(app)
          .post("/import/customers")
          .attach("file", Buffer.from("Some content"), "test.csv")
          .set("mock-error-code", "UNKNOWN_ERROR")
          .set("mock-error-message", "Unknown error");
    
        expect(res.status).toBe(500);
        expect(res.body.msg).toBe("File upload error");
      });
    });