test: refactor mocks to use spyOn for better isolation

This commit is contained in:
syntaxbullet
2025-12-19 13:31:14 +01:00
parent 4e228bb7a3
commit 1f7679e5a1
4 changed files with 62 additions and 72 deletions

View File

@@ -1,7 +1,8 @@
import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"; import { describe, it, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
import { lootdropService } from "./lootdrop.service"; import { lootdropService } from "./lootdrop.service";
import { lootdrops } from "@/db/schema"; import { lootdrops } from "@/db/schema";
import { eq, and, isNull } from "drizzle-orm"; import { eq, and, isNull } from "drizzle-orm";
import { economyService } from "./economy.service";
// Mock dependencies BEFORE using service functionality // Mock dependencies BEFORE using service functionality
const mockInsert = mock(); const mockInsert = mock();
@@ -34,16 +35,6 @@ mock.module("@/lib/DrizzleClient", () => {
}; };
}); });
// Mock EconomyService
const mockModifyUserBalance = mock();
mock.module("./economy.service", () => {
return {
economyService: {
modifyUserBalance: mockModifyUserBalance
}
};
});
// Mock Config // Mock Config
mock.module("@/lib/config", () => ({ mock.module("@/lib/config", () => ({
config: { config: {
@@ -63,6 +54,7 @@ mock.module("@/lib/config", () => ({
describe("lootdropService", () => { describe("lootdropService", () => {
let originalRandom: any; let originalRandom: any;
let mockModifyUserBalance: any;
beforeEach(() => { beforeEach(() => {
mockInsert.mockClear(); mockInsert.mockClear();
@@ -74,7 +66,6 @@ describe("lootdropService", () => {
mockWhere.mockClear(); mockWhere.mockClear();
mockSelect.mockClear(); mockSelect.mockClear();
mockFrom.mockClear(); mockFrom.mockClear();
mockModifyUserBalance.mockClear();
// Reset internal state // Reset internal state
(lootdropService as any).channelActivity = new Map(); (lootdropService as any).channelActivity = new Map();
@@ -82,10 +73,14 @@ describe("lootdropService", () => {
// Mock Math.random // Mock Math.random
originalRandom = Math.random; originalRandom = Math.random;
// Spy
mockModifyUserBalance = spyOn(economyService, 'modifyUserBalance').mockResolvedValue({} as any);
}); });
afterEach(() => { afterEach(() => {
Math.random = originalRandom; Math.random = originalRandom;
mockModifyUserBalance.mockRestore();
}); });
describe("processMessage", () => { describe("processMessage", () => {

View File

@@ -1,7 +1,9 @@
import { describe, it, expect, mock, beforeEach } from "bun:test"; import { describe, it, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
import { inventoryService } from "./inventory.service"; import { inventoryService } from "./inventory.service";
import { inventory, items, userTimers } from "@/db/schema"; import { inventory, items, userTimers } from "@/db/schema";
import { app } from "@/index"; // We don't need app here, removed. // Helper to mock resolved value for spyOn
import { economyService } from "@/modules/economy/economy.service";
import { levelingService } from "@/modules/leveling/leveling.service";
// Mock dependencies // Mock dependencies
const mockFindFirst = mock(); const mockFindFirst = mock();
@@ -56,20 +58,6 @@ mock.module("@/lib/DrizzleClient", () => {
}; };
}); });
const mockModifyUserBalance = mock();
mock.module("@/modules/economy/economy.service", () => ({
economyService: {
modifyUserBalance: mockModifyUserBalance
}
}));
const mockAddXp = mock();
mock.module("@/modules/leveling/leveling.service", () => ({
levelingService: {
addXp: mockAddXp
}
}));
mock.module("@/lib/config", () => ({ mock.module("@/lib/config", () => ({
config: { config: {
inventory: { inventory: {
@@ -80,6 +68,9 @@ mock.module("@/lib/config", () => ({
})); }));
describe("inventoryService", () => { describe("inventoryService", () => {
let mockModifyUserBalance: any;
let mockAddXp: any;
beforeEach(() => { beforeEach(() => {
mockFindFirst.mockReset(); mockFindFirst.mockReset();
mockFindMany.mockReset(); mockFindMany.mockReset();
@@ -92,9 +83,16 @@ describe("inventoryService", () => {
mockWhere.mockClear(); mockWhere.mockClear();
mockSelect.mockClear(); mockSelect.mockClear();
mockFrom.mockClear(); mockFrom.mockClear();
mockModifyUserBalance.mockClear();
mockAddXp.mockClear();
mockOnConflictDoUpdate.mockClear(); mockOnConflictDoUpdate.mockClear();
// Setup Spies
mockModifyUserBalance = spyOn(economyService, 'modifyUserBalance').mockResolvedValue({} as any);
mockAddXp = spyOn(levelingService, 'addXp').mockResolvedValue({} as any);
});
afterEach(() => {
mockModifyUserBalance.mockRestore();
mockAddXp.mockRestore();
}); });
describe("addItem", () => { describe("addItem", () => {
@@ -124,7 +122,8 @@ describe("inventoryService", () => {
const result = await inventoryService.addItem("1", 1, 5n); const result = await inventoryService.addItem("1", 1, 5n);
expect(result.quantity).toBe(15n); expect(result).toBeDefined();
expect(result?.quantity).toBe(15n);
expect(mockUpdate).toHaveBeenCalledWith(inventory); expect(mockUpdate).toHaveBeenCalledWith(inventory);
expect(mockSet).toHaveBeenCalledWith({ quantity: 15n }); expect(mockSet).toHaveBeenCalledWith({ quantity: 15n });
}); });
@@ -163,7 +162,8 @@ describe("inventoryService", () => {
const result = await inventoryService.removeItem("1", 1, 5n); const result = await inventoryService.removeItem("1", 1, 5n);
expect(mockDelete).toHaveBeenCalledWith(inventory); expect(mockDelete).toHaveBeenCalledWith(inventory);
expect(result.quantity).toBe(0n); expect(result).toBeDefined();
expect(result?.quantity).toBe(0n);
}); });
it("should throw if insufficient quantity", async () => { it("should throw if insufficient quantity", async () => {

View File

@@ -1,7 +1,9 @@
import { describe, it, expect, mock, beforeEach } from "bun:test"; import { describe, it, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
import { questService } from "./quest.service"; import { questService } from "./quest.service";
import { userQuests } from "@/db/schema"; import { userQuests } from "@/db/schema";
import { eq, and } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import { economyService } from "@/modules/economy/economy.service";
import { levelingService } from "@/modules/leveling/leveling.service";
// Mock dependencies // Mock dependencies
const mockFindFirst = mock(); const mockFindFirst = mock();
@@ -45,22 +47,10 @@ mock.module("@/lib/DrizzleClient", () => {
}; };
}); });
// Mock External Services
const mockModifyUserBalance = mock();
mock.module("@/modules/economy/economy.service", () => ({
economyService: {
modifyUserBalance: mockModifyUserBalance
}
}));
const mockAddXp = mock();
mock.module("@/modules/leveling/leveling.service", () => ({
levelingService: {
addXp: mockAddXp
}
}));
describe("questService", () => { describe("questService", () => {
let mockModifyUserBalance: any;
let mockAddXp: any;
beforeEach(() => { beforeEach(() => {
mockFindFirst.mockReset(); mockFindFirst.mockReset();
mockFindMany.mockReset(); mockFindMany.mockReset();
@@ -71,8 +61,15 @@ describe("questService", () => {
mockSet.mockClear(); mockSet.mockClear();
mockWhere.mockClear(); mockWhere.mockClear();
mockOnConflictDoNothing.mockClear(); mockOnConflictDoNothing.mockClear();
mockModifyUserBalance.mockClear();
mockAddXp.mockClear(); // Setup Spies
mockModifyUserBalance = spyOn(economyService, 'modifyUserBalance').mockResolvedValue({} as any);
mockAddXp = spyOn(levelingService, 'addXp').mockResolvedValue({} as any);
});
afterEach(() => {
mockModifyUserBalance.mockRestore();
mockAddXp.mockRestore();
}); });
describe("assignQuest", () => { describe("assignQuest", () => {

View File

@@ -1,6 +1,8 @@
import { describe, it, expect, mock, beforeEach, setSystemTime } from "bun:test"; import { describe, it, expect, mock, beforeEach, afterEach, spyOn } from "bun:test";
import { TradeService } from "./trade.service"; import { TradeService } from "./trade.service";
import { itemTransactions } from "@/db/schema"; import { itemTransactions } from "@/db/schema";
import { economyService } from "@/modules/economy/economy.service";
import { inventoryService } from "@/modules/inventory/inventory.service";
// Mock dependencies // Mock dependencies
const mockInsert = mock(); const mockInsert = mock();
@@ -22,38 +24,34 @@ mock.module("@/lib/DrizzleClient", () => {
}; };
}); });
// Mock External Services
const mockModifyUserBalance = mock();
mock.module("@/modules/economy/economy.service", () => ({
economyService: {
modifyUserBalance: mockModifyUserBalance
}
}));
const mockAddItem = mock();
const mockRemoveItem = mock();
mock.module("@/modules/inventory/inventory.service", () => ({
inventoryService: {
addItem: mockAddItem,
removeItem: mockRemoveItem
}
}));
describe("TradeService", () => { describe("TradeService", () => {
const userA = { id: "1", username: "UserA" }; const userA = { id: "1", username: "UserA" };
const userB = { id: "2", username: "UserB" }; const userB = { id: "2", username: "UserB" };
let mockModifyUserBalance: any;
let mockAddItem: any;
let mockRemoveItem: any;
beforeEach(() => { beforeEach(() => {
mockModifyUserBalance.mockClear();
mockAddItem.mockClear();
mockRemoveItem.mockClear();
mockInsert.mockClear(); mockInsert.mockClear();
mockValues.mockClear(); mockValues.mockClear();
// Clear sessions // Clear sessions
(TradeService as any).sessions.clear(); (TradeService as any).sessions.clear();
// Spies
mockModifyUserBalance = spyOn(economyService, 'modifyUserBalance').mockResolvedValue({} as any);
mockAddItem = spyOn(inventoryService, 'addItem').mockResolvedValue({} as any);
mockRemoveItem = spyOn(inventoryService, 'removeItem').mockResolvedValue({} as any);
}); });
afterEach(() => {
mockModifyUserBalance.mockRestore();
mockAddItem.mockRestore();
mockRemoveItem.mockRestore();
});
describe("createSession", () => { describe("createSession", () => {
it("should create a new session", () => { it("should create a new session", () => {
const session = TradeService.createSession("thread1", userA, userB); const session = TradeService.createSession("thread1", userA, userB);
@@ -108,7 +106,7 @@ describe("TradeService", () => {
TradeService.addItem("thread1", "1", { id: 10, name: "Sword" }, 2n); TradeService.addItem("thread1", "1", { id: 10, name: "Sword" }, 2n);
const session = TradeService.getSession("thread1"); const session = TradeService.getSession("thread1");
expect(session?.userA.offer.items[0].quantity).toBe(3n); expect(session?.userA.offer.items[0]!.quantity).toBe(3n);
}); });
}); });