Jest? and What are sub, fake, mock?

npm install –save-dev jest @types/jest

npm install –save-dev ts-jest

npx ts-jest config:init will generate file jest.config.js

//jest.config.js content 

/** @type {import('ts-jest').JestConfigWithTsJest} **/
module.exports = {
  testEnvironment: "node",
  transform: {
    "^.+\\.tsx?$": ["ts-jest", {}],  // Fix regex for .ts and .tsx files
  },
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/$1",//let jest recognize the path like "@/helpers/sum" 
  },
};

create __tests__ folder under root path of Next.js project, and create xxx.test.ts under this folder.

//sum.test.ts
import { sum } from "@/helpers/sum";

describe("the sum function", () => {
    test("two plus two is four", () => {
        expect(sum([2,2])).toBe(4);
    })
    test("minus eight plus four is minus four", () => {
        expect(sum([-8,4])).toBe(-4);
    })
    test("two plus two plus minus four is zero", () => {
        expect(sum([2, 2, -4])).toBe(0);
    })
})
//fibonacci.test.ts
import { fibonacci } from "@/helpers/fibonacci";

jest.mock("@/helpers/sum");

describe("the fibonacci sequence", () => {

    test("with a length of 0 is ", () => {
        expect(fibonacci(0)).toBe("");
    })

    test("with a length of 5 is '0, 1, 1, 2, 3' ", () => {
        expect(fibonacci(5)).toBe("0, 1, 1, 2, 3");
    })
})

create a __mocks__ folder under the /helpers foder and put a double test file. This mean create a fake sum.ts put in __mocks__ folder. the jest.mock(“@/helps/sum”); will use this dobule test file to test.

//sum.ts under /helpers/__mocks__ folder
type resultMap = {
    [key: string]: number;
}
const results: resultMap = {
    "0+0": 0,
    "0+1": 1,
    "1+0": 1,
    "1+1": 2,
    "2+1": 3
};
const sum = (data: number[]): number => {
    return results[data.join("+")];
}
export { sum };
//sum.ts under /helpers folder, this actual implement.
export const sum = (data: number[]): number =>  {
    return data.reduce((a, b) => a + b);
}

//db-connect.test.ts
import dbConnect from "@/middleware/db-connect";
import mongoose from "mongoose";
import { MongoMemoryServer } from "mongodb-memory-server";

describe("dbConnect", () => {

    let connection: any;

    afterEach(async () => {
        jest.clearAllMocks();
        await connection.stop();
        await mongoose.disconnect();
    });

    afterAll(async () => {
        jest.restoreAllMocks();
    })

    test("calls MongoMemoryServer.create()", async () => {
        const spy = jest.spyOn(MongoMemoryServer, "create");
        connection = await dbConnect();
        expect(spy).toHaveBeenCalled();
    })

    test("calls mongoose.disconnect()", async () => {
        const spy = jest.spyOn(mongoose, "disconnect");
        connection = await dbConnect();
        expect(spy).toHaveBeenCalled();
    })

    test("calls mongoose.connect()", async () => {
        const spy = jest.spyOn(mongoose, "connect");
        connection = await dbConnect();
        const MONGO_URI = connection.getUri();
        expect(spy).toHaveBeenCalledWith(MONGO_URI, { dbName: "Weather" });
    })

})

use npx jest to run unit test or use npx jest –coverage to run unit test.

Jest is Unit Test Frame for JavaScript. The purpose is to isolate the test object during the test process to avoid affecting external dependencies (such as databases, APIs, file systems, etc.) and to improve the speed and stability of the test.

In testing, we use Test Doubles to simulate real dependent objects. The three most common ones are Fake, Stub, and Mock.

1️⃣ Fake (Simplified Implementation)

class FakeDatabase {
    private data = new Map<string, string>();

    save(key: string, value: string) {
        this.data.set(key, value);
    }

    get(key: string) {
        return this.data.get(key) || null;
    }
}

const fakeDB = new FakeDatabase();
fakeDB.save("user1", "Alice");
console.log(fakeDB.get("user1")); // "Alice"

📌 When to use?

  • When testing CRUD operations without using a real database.

2️⃣ Stub (Hardcoded Response)

class UserService {
    getUserName(userId: string): string {
        return "Real User"; // 這是正式環境的行為
    }
}

// 測試時用 Stub 取代
class StubUserService {
    getUserName(userId: string): string {
        return "Test User"; // 測試時固定回傳
    }
}

const service = new StubUserService();
console.log(service.getUserName("123")); // "Test User"

📌 When to use?

  • When testing without real API responses or database queries.

3️⃣ Mock (Tracks Calls & Arguments)

const mockFn = jest.fn().mockReturnValue("Mocked Response");

console.log(mockFn()); // "Mocked Response"
console.log(mockFn.mock.calls.length); // 1

📌 When to use?

  • When testing if a function was called with the correct parameters.

🔍 Comparison Table

TypeMain FeatureWhen to Use?
FakeA simplified version of the real dependencyTesting CRUD, caching, in-memory databases
StubAlways returns predefined valuesReplacing API responses, avoiding complex computations
MockTracks function calls & argumentsEnsuring a function is called correctly

📌 Why Use Fakes, Stubs, and Mocks?

  1. Avoid external dependencies
    • Prevent tests from depending on real databases, APIs, or services.
  2. Improve test reliability
    • Ensure that tests run consistently regardless of external factors.
  3. Increase test speed
    • Avoid slow network requests or database queries.

🎯 Practical Example in a GraphQL + Mongoose Project

If you’re using Mongoose + GraphQL, you can mock findAllLocations() like this:

typescript

import { findAllLocations } from "mongoose/locations/services";

// Mock the function using Jest
jest.mock("mongoose/locations/services", () => ({
    findAllLocations: jest.fn().mockResolvedValue([{ location_id: "123", name: "Test Location" }]),
}));

test("GraphQL Query allLocations", async () => {
    const result = await findAllLocations();
    expect(result).toEqual([{ location_id: "123", name: "Test Location" }]);
    expect(findAllLocations).toHaveBeenCalledTimes(1);
});

🔹 What happens here?

  • jest.mock("mongoose/locations/services", ... )
    • Replaces everything inside "mongoose/locations/services" with the given mock implementation.
  • findAllLocations: jest.fn().mockResolvedValue([...])
    • Creates a mock function that always returns a predefined response ([{ location_id: "123", name: "Test Location" }]).
    • mockResolvedValue([...]) makes it return a Promise, simulating an async function.

✅ Summary

Fakes, Stubs, and Mocks help you replace dependencies in tests:

  1. Fake → A working but simplified implementation (e.g., in-memory database).
  2. Stub → A hardcoded response with no real logic (e.g., fixed API response).
  3. Mock → Records function interactions (e.g., checking if API was called).

Using these techniques makes your tests faster, more reliable, and independent of external systems! 🚀

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *