Hi everyone ![]()
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:
Successfully creating a new store (201)
Zod validation errors (400)
User not found (404)
Store name already exists (409)
Unexpected error → handlerError (500)
Example of my test setup:
- Mocking
res.status()andres.json() - Spying on
user.findOneandstore.findOne - Using
vi.clearAllMocks()inbeforeEach
My questions are:
Does this level of unit testing make sense for a controller like this?
Would you simplify or split these tests differently?
At what point would you switch from unit tests to integration tests here?
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 ![]()
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);
});
});