Skip to main content
Glama
database.service.test.ts5.48 kB
// --- 1. Mock the 'mongodb' library --- // We provide a mock implementation for MongoClient and its methods. const mockConnect = jest.fn(); const mockDb = jest.fn(); const mockClose = jest.fn(); // This is the mock MongoClient constructor const mockMongoClient = jest.fn(() => ({ connect: mockConnect, db: mockDb, close: mockClose, })); jest.mock("mongodb", () => { // This replaces the actual 'mongodb' library with our mock version return { MongoClient: mockMongoClient, }; }); // --- 2. Import the *actual* service functions we want to test --- import * as dbService from "../database.service"; import { Db } from "mongodb"; describe("DatabaseService Lifecycle", () => { const MOCK_DB_NAME = "youtube_niche_analysis"; const MOCK_CONN_STRING = "mongodb://test-connection"; const mockDbInstance = {} as Db; // A dummy Db object for our mock to return let mockClientInstance: any; beforeEach(() => { // Reset mocks as before mockMongoClient.mockClear(); mockConnect.mockClear(); mockDb.mockClear(); mockClose.mockClear(); // Create a single, consistent instance for the test mockClientInstance = { connect: mockConnect, db: mockDb, close: mockClose, }; // The constructor mock now returns this specific instance mockMongoClient.mockReturnValue(mockClientInstance); // The connect mock now resolves with that same instance mockConnect.mockResolvedValue(mockClientInstance); mockDb.mockReturnValue(mockDbInstance); }); afterEach(async () => { // --- 3. This is CRITICAL for test isolation --- // Use the real disconnect function to reset the singleton's internal state await dbService.disconnectFromDatabase(); }); it("should throw an error if getDb is called before initialization", async () => { // We expect the real getDb function to reject because _connectionString is null await expect(dbService.getDb()).rejects.toThrow( "Database not initialized. Call initializeDatabase() first." ); }); it("should initialize, connect lazily on first getDb call, and return the db instance", async () => { // Initialize the service with our test connection string dbService.initializeDatabase(MOCK_CONN_STRING); // At this point, no connection should have been made yet expect(mockConnect).not.toHaveBeenCalled(); // The first call to getDb should trigger the connection const db = await dbService.getDb(); // Now, we verify the actual logic ran expect(mockMongoClient).toHaveBeenCalledWith(MOCK_CONN_STRING); expect(mockConnect).toHaveBeenCalledTimes(1); expect(mockDb).toHaveBeenCalledWith(MOCK_DB_NAME); expect(db).toBe(mockDbInstance); }); it("should reuse the existing connection promise on subsequent calls", async () => { dbService.initializeDatabase(MOCK_CONN_STRING); // Call getDb multiple times await dbService.getDb(); await dbService.getDb(); const db = await dbService.getDb(); // The core test of the singleton: connect should only be called ONCE expect(mockConnect).toHaveBeenCalledTimes(1); expect(db).toBe(mockDbInstance); }); it("should handle connection failure and allow for a retry", async () => { dbService.initializeDatabase(MOCK_CONN_STRING); // --- 1. Simulate a failure --- // Use mockRejectedValueOnce to make the first call fail mockConnect.mockRejectedValueOnce(new Error("Connection failed")); // Expect the first call to getDb to fail await expect(dbService.getDb()).rejects.toThrow("Connection failed"); // Verify that the connection was attempted expect(mockConnect).toHaveBeenCalledTimes(1); // --- 2. Simulate a success on the next try --- // The service should have reset its promise, so a second call should try again. // Our beforeEach already configured the default mock to succeed. await expect(dbService.getDb()).resolves.toBe(mockDbInstance); // The critical assertion: connect was called a SECOND time. expect(mockConnect).toHaveBeenCalledTimes(2); }); it("should handle concurrent calls by creating only one connection", async () => { dbService.initializeDatabase(MOCK_CONN_STRING); // Fire off two calls to getDb concurrently without awaiting them individually const promise1 = dbService.getDb(); const promise2 = dbService.getDb(); // Await them both together const [db1, db2] = await Promise.all([promise1, promise2]); // The critical assertion: connect was only ever called ONCE for both. expect(mockConnect).toHaveBeenCalledTimes(1); // And both calls resolved to the exact same Db instance. expect(db1).toBe(mockDbInstance); expect(db2).toBe(mockDbInstance); }); it("should disconnect and allow for a new connection", async () => { dbService.initializeDatabase(MOCK_CONN_STRING); // First connection await dbService.getDb(); expect(mockConnect).toHaveBeenCalledTimes(1); // Disconnect using the real service function await dbService.disconnectFromDatabase(); expect(mockClose).toHaveBeenCalledTimes(1); // Re-initialize and connect again dbService.initializeDatabase("mongodb://new-string"); await dbService.getDb(); // A new connection should have been made expect(mockConnect).toHaveBeenCalledTimes(2); // Called once before, once now expect(mockMongoClient).toHaveBeenCalledWith("mongodb://new-string"); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kirbah/mcp-youtube'

If you have feedback or need assistance with the MCP directory API, please join our Discord server