Jest: Mongoose

///mongoose/weather/__mocks__/model.ts
import { projectUpdateInfoSubscribe } from "next/dist/build/swc/generated-native";
import { WeatherInterface } from "../interface";

type param = {
    [key: string]: string;
};

const WeatherModel = {
    create: jest.fn((newData: WeatherInterface) => Promise.resolve(true)),
    findOne: jest.fn(({ zip: paramZip }: param) => Promise.resolve(true)),
    updateOne: jest.fn(({ zip: paramZip }: param, newData: WeatherInterface) => Promise.resolve(true)),
    deleteOne: jest.fn(({ zip: paramZip }: param) => Promise.resolve(true))
};

export default WeatherModel;

jest.fn(): Jest provides a mock function that replaces the real function so that you can monitor its behavior, for example:

  • Has it ever been called?
  • How many times have you been called?
  • What parameters are passed in when it is called?
  • You can let it return the value you specify
//interface.ts
export declare interface WeatherInterface {
    zip: string;
    weather: string;
    tempC: string;
    tempF: string;
    friends: string[];
}
//schema.ts
import { Schema } from "mongoose";
import { WeatherInterface } from "./interface";

export const WeatherSchema = new Schema<WeatherInterface>({
    zip: {
        type: String, 
        required: true,
    },
    weather: {
        type: String,
        required: true,
    },
    tempC: {
        type: String,
        required: true,
    },
    tempF: {
        type: String,
        required: true,
    },
    friends: {
        type: [String],
        required: true,
    }
});;
//model.ts
import mongoose, { model } from "mongoose";
import { WeatherInterface } from "./interface";
import { WeatherSchema } from "./schema";

export default mongoose.models.Weather ||
    model<WeatherInterface>("Weather", WeatherSchema);
//service.ts
import WeatherModel from  "./model";
import { WeatherInterface } from "./interface";

export async function storeDocument(doc: WeatherInterface): Promise<boolean> {
    try {
        await WeatherModel.create(doc);
    } catch (error) {
        return false;
    }
    return true;
}

export async function findByZip(paramZip: string): Promise<Array<WeatherInterface> | null> {
    try {
        return await WeatherModel.findOne({zip: paramZip});
    } catch (error) {
        console.log(error);
    }
    return [];
}

export async function updateByZip(paramZip: string, newData: WeatherInterface): Promise<boolean> {
    try {
        await WeatherModel.updateOne({zip: paramZip}, newData);
        return true;
    } catch (error) {
        console.log(error);
    }
    return false;
}

export async function deleteByZip(paramZip: string): Promise<boolean> {
    try {
        await WeatherModel.deleteOne({zip: paramZip});
        return true;
    } catch (error) {
        console.log(error);
    }
    return false;
}
//db-connect.ts
import mongoose from "mongoose";
import { MongoMemoryServer } from "mongodb-memory-server";
import { storeDocument } from "@/mongoose/weather/services";

let isConnected: boolean = false; // 用來追蹤是否已經有連線

async function dbConnect(): Promise<any | string> {
    // 如果已經連接過了,就直接返回已經建立的連線
    if (isConnected) {
        return mongoose.connection;
    }

    // 如果尚未建立連線,則創建新的連線
    try {
        const mongoServer = await MongoMemoryServer.create();
        const MONGO_URI = mongoServer.getUri();

        await mongoose.disconnect(); // 確保任何現有的連線已經斷開
        await mongoose.connect(MONGO_URI, {
            dbName: "Weather"
        });

        await storeDocument({
            zip: "96815",
            weather: "sunny",
            tempC: "25C",
            tempF: "70F",
            friends: ["96814", "96826"]
        });

        await storeDocument({
            zip: "96814",
            weather: "rainy",
            tempC: "20C",
            tempF: "68F",
            friends: ["96815", "96826"]
        });

        await storeDocument({
            zip: "96826",
            weather: "rainy",
            tempC: "30C",
            tempF: "86F",
            friends: ["96815", "96814"]
        });

        isConnected = true; // 設置連線成功的標誌

        mongoose.connection.on("disconnected", () => {
            isConnected = false;
        });

        return mongoServer;
    } catch (error) {
        throw error;
    }
}

export default dbConnect;
//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" });
    })

})
//services.test.ts
import { WeatherInterface } from "@/mongoose/weather/interface";

import {
    findByZip,
    storeDocument,
    updateByZip,
    deleteByZip
} from "@/mongoose/weather/services";

import WeatherModel from "@/mongoose/weather/model";

jest.mock("@/mongoose/weather/model");


describe("the weather services", () => {

    let doc: WeatherInterface = {
        zip: "test",
        weather: "weather",
        tempC: "00",
        tempF: "01",
        friends: []
    };

    afterEach(async () => {
        jest.clearAllMocks();
    })

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

    describe("API storeDocument", () => {

        test("returns true", async () => {
            const result = await storeDocument(doc);
            expect(result).toBeTruthy();
        })

        test("passes the document to Model.create", async () => {
            const spy = jest.spyOn(WeatherModel, "create");
            await storeDocument(doc);
            expect(spy).toHaveBeenCalledWith(doc);
        })
    })

    describe("API findByZip", () => {
        test("returns true", async () => {
            const result = await findByZip(doc.zip);
            expect(result).toBeTruthy();
        })

        test("passes the zip code to Model.findOne()", async () => {
            const spy = jest.spyOn(WeatherModel, "findOne");
            await findByZip(doc.zip);
            expect(spy).toHaveBeenCalledWith({ zip: doc.zip });
        })
    })

    describe("API updateByZip", () => {
        test("returns true", async () => {
            const result = await updateByZip(doc.zip, doc);
            expect(result).toBeTruthy();
        })

        test("passes the zip code and new data to Model.updateOne()", async () => {
            const spy = jest.spyOn(WeatherModel, "updateOne");
            await updateByZip(doc.zip, doc);
            expect(spy).toHaveBeenCalledWith({ zip: doc.zip }, doc);
        })
    })

    describe("API deleteByZip", () => {
        test("returns true", async () => {
            const result = await deleteByZip(doc.zip);
            expect(result).toBeTruthy();
        })

        test("passes the zip code to Model.deleteOne()", async () => {
            const spy = jest.spyOn(WeatherModel, "deleteOne");
            await deleteByZip(doc.zip);
            expect(spy).toHaveBeenCalledWith({ zip: doc.zip });
        })
    })


});

發佈留言

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