How would you improve these Vitest unit tests for an Express controller?

Hi everyone :waving_hand:

I’m writing unit tests for an Express controller using Vitest + TypeScript + Node,
and I’d love some feedback from more experienced devs.

My goal was to fully test a createStore controller that handles:

  • Zod validation
  • MongoDB (Mongoose) models
  • Business rules (user exists, store name uniqueness)
  • Error handling

Instead of integration tests, I decided to mock everything (models, response object, errors).

Here are the main scenarios I’m covering:

:white_check_mark: Successfully creating a new store (201)
:cross_mark: Zod validation errors (400)
:cross_mark: User not found (404)
:cross_mark: Store name already exists (409)
:cross_mark: Unexpected error → handlerError (500)

Example of my test setup:

  • Mocking res.status() and res.json()
  • Spying on user.findOne and store.findOne
  • Using vi.clearAllMocks() in beforeEach

My questions are:

:one: Does this level of unit testing make sense for a controller like this?
:two: Would you simplify or split these tests differently?
:three: At what point would you switch from unit tests to integration tests here?
:four: Any Vitest + Express testing best practices I might be missing?

I’m especially curious how you structure controller tests in real-world Node.js projects.

Thanks in advance :raising_hands:

import handlerError from "@/middleware/handlerError.js";
import store, { IStore } from "@/models/store.js";
import user, { IUser } from "@/models/user.js";
import { dataCreateStore, typeCreateStore } from "@/zod/createStore.js";
import { errorMessage } from "@/zod/errorMessage.js";
import { type Request, type Response } from "express";

const checkingIfUserExist = async (id: string): Promise<IUser | null> => {
  return await user.findOne({ _id: id, role: "client", blocked: false });
};

const searchByStore = async (myStore: string): Promise<IStore | null> => {
  return await store.findOne({ store: myStore, blocked: false });
};

const createNewStore = async (req: Request, res: Response): Promise<void> => {
  const validationData = dataCreateStore.safeParse(req.body);
  if (!validationData.success) {
    res.status(400).json({
      success: false,
      error: {
        type: errorMessage.type,
        message: errorMessage.message,
        details: validationData.error.issues,
      },
    });
    return;
  }

  const data: typeCreateStore = validationData.data;

  try {
    const userExist = await checkingIfUserExist(data.userId);
    if (!userExist) {
      res.status(404).json({
        success: false,
        error: {
          type: "USER_NOT_FOUND",
          message: "We did not find any user with that ID.",
        },
      });
      return;
    }

    const nameStoreExist = await searchByStore(data.store);
    if (nameStoreExist) {
      res.status(409).json({
        success: false,
        error: {
          type: "STORE_NAME_EXISTS",
          message: "There's already a store with that name.",
        },
      });
      return;
    }

    await store.create({
      userId: data.userId,
      store: data.store,
      bio: data.bio,
      location: {
        state: data.location.state,
        city: data.location.city,
        zipcode: data.location.zipcode,
      },
    });
    res.status(201).json({
      success: true,
      message: "Store successfully created!",
    });
  } catch (error: unknown) {
    handlerError(
      error,
      res,
      500,
      "IMPOSSIBLE_CREATE_NEW_STORE",
      "Não foi possível criar uma nova loja"
    );
  }
};

export default createNewStore;
import { describe, test, vi, beforeEach, expect } from "vitest";
import { type Request } from "express";
import mongoose from "mongoose";

import store from "@/models/store.js";
import user from "@/models/user.js";
import createNewStore from "@/controllers/store/create.js";

const mockResponse = () => {
  const res: any = {};

  res.status = vi.fn().mockReturnValue(res);
  res.json = vi.fn().mockReturnValue(res);
  return res;
};

describe("Create Store Controller ", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  const idMongo = new mongoose.Types.ObjectId().toString();

  test("You must create a new store.", async () => {
    const req = {
      body: {
        userId: idMongo,
        CNPJ: "1234567",
        store: "Zip",
        bio: "Clothing store",
        mainCategory: "clothes",
        location: {
          country: "Brasil",
          state: "Rio de janeiro",
          city: "RJ",
          zipcode: "22770482",
          address: "Paul Street",
        },
        schedules: {
          monday: { open: false, start: "", end: "" },
          tuesday: { open: false, start: "", end: "" },
          wednesday: { open: false, start: "", end: "" },
          thursday: { open: false, start: "", end: "" },
          friday: { open: false, start: "", end: "" },
          saturday: { open: false, start: "", end: "" },
          sunday: { open: false, start: "", end: "" },
        },
        policies: {
          returnPolicy: "",
          shippingPolicy: "",
          processingTime: "",
        },
      },
    } as unknown as Request;

    const res = mockResponse();

    vi.spyOn(user, "findOne").mockResolvedValue({
      _id: idMongo,
      role: "client",
      blocked: false,
    } as any);

    vi.spyOn(store, "findOne").mockResolvedValue(null);

    vi.spyOn(store, "create").mockResolvedValue({
      userId: idMongo,
      store: "Zip",
      bio: "Clothing store",
    } as any);

    await createNewStore(req, res);

    expect(res.status).toHaveBeenCalledWith(201);
    expect(res.json).toHaveBeenCalledWith({
      success: true,
      message: "Store successfully created!",
    } as any);
  });

  test("It should return a validation error in Zod.", async () => {
    const req = {
      body: {
        userId: "",
        CNPJ: "1234567",
        store: "Zip",
        bio: "Clothing store",
        mainCategory: "clothes",
        location: {
          country: "Brasil",
          state: "Rio de janeiro",
          city: "RJ",
          zipcode: "22770482",
          address: "Paul Street",
        },
        schedules: {
          monday: { open: false, start: "", end: "" },
          tuesday: { open: false, start: "", end: "" },
          wednesday: { open: false, start: "", end: "" },
          thursday: { open: false, start: "", end: "" },
          friday: { open: false, start: "", end: "" },
          saturday: { open: false, start: "", end: "" },
          sunday: { open: false, start: "", end: "" },
        },
        policies: {
          returnPolicy: "",
          shippingPolicy: "",
          processingTime: "",
        },
      },
    } as unknown as Request;

    const res = mockResponse();

    await createNewStore(req, res);

    expect(res.status).toHaveBeenCalledWith(400);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      error: {
        type: expect.any(String),
        message: expect.any(String),
        details: expect.any(Array),
      },
    } as any);
  });

  test("Progress should be blocked if the user does not exist.", async () => {
    const req = {
      body: {
        userId: idMongo,
        CNPJ: "1234567",
        store: "Zip",
        bio: "Clothing store",
        mainCategory: "clothes",
        location: {
          country: "Brasil",
          state: "Rio de janeiro",
          city: "RJ",
          zipcode: "22770482",
          address: "Paul Street",
        },
        schedules: {
          monday: { open: false, start: "", end: "" },
          tuesday: { open: false, start: "", end: "" },
          wednesday: { open: false, start: "", end: "" },
          thursday: { open: false, start: "", end: "" },
          friday: { open: false, start: "", end: "" },
          saturday: { open: false, start: "", end: "" },
          sunday: { open: false, start: "", end: "" },
        },
        policies: {
          returnPolicy: "",
          shippingPolicy: "",
          processingTime: "",
        },
      },
    } as unknown as Request;

    const res = mockResponse();

    vi.spyOn(user, "findOne").mockResolvedValue(null);

    await createNewStore(req, res);

    expect(res.status).toHaveBeenCalledWith(404);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      error: {
        type: "USER_NOT_FOUND",
        message: "We did not find any user with that ID.",
      },
    } as any);
  });

  test("It should prohibit the creation of a similar store.", async () => {
    const req = {
      body: {
        userId: idMongo,
        CNPJ: "1234567",
        store: "Zip",
        bio: "Clothing store",
        mainCategory: "clothes",
        location: {
          country: "Brasil",
          state: "Rio de janeiro",
          city: "RJ",
          zipcode: "22770482",
          address: "Paul Street",
        },
        schedules: {
          monday: { open: false, start: "", end: "" },
          tuesday: { open: false, start: "", end: "" },
          wednesday: { open: false, start: "", end: "" },
          thursday: { open: false, start: "", end: "" },
          friday: { open: false, start: "", end: "" },
          saturday: { open: false, start: "", end: "" },
          sunday: { open: false, start: "", end: "" },
        },
        policies: {
          returnPolicy: "",
          shippingPolicy: "",
          processingTime: "",
        },
      },
    } as unknown as Request;

    const res = mockResponse();

    vi.spyOn(user, "findOne").mockResolvedValue({
      _id: idMongo,
      role: "client",
      blocked: false,
    } as any);

    vi.spyOn(store, "findOne").mockResolvedValue({
      store: "Zip",
    } as any);

    await createNewStore(req, res);

    expect(res.status).toHaveBeenCalledWith(409);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      error: {
        type: "STORE_NAME_EXISTS",
        message: "There's already a store with that name.",
      },
    } as any);
  });

  test("It should throw me into a handlerError.", async () => {
    const req = {
      body: {
        userId: idMongo,
        CNPJ: "1234567",
        store: "Zip",
        bio: "Clothing store",
        mainCategory: "clothes",
        location: {
          country: "Brasil",
          state: "Rio de janeiro",
          city: "RJ",
          zipcode: "22770482",
          address: "Paul Street",
        },
        schedules: {
          monday: { open: false, start: "", end: "" },
          tuesday: { open: false, start: "", end: "" },
          wednesday: { open: false, start: "", end: "" },
          thursday: { open: false, start: "", end: "" },
          friday: { open: false, start: "", end: "" },
          saturday: { open: false, start: "", end: "" },
          sunday: { open: false, start: "", end: "" },
        },
        policies: {
          returnPolicy: "",
          shippingPolicy: "",
          processingTime: "",
        },
      },
    } as unknown as Request;

    const res = mockResponse();

    vi.spyOn(console, "error").mockImplementation(() => {});

    vi.spyOn(user, "findOne").mockRejectedValue(new Error("Unexpected error"));

    await createNewStore(req, res);

    expect(res.status).toHaveBeenCalledWith(500);
    expect(res.json).toHaveBeenCalledWith({
      success: false,
      error: {
        type: "IMPOSSIBLE_CREATE_NEW_STORE",
        message: "It was not possible to create a new store.",
      },
    } as any);
  });
});