@yama111111

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Next.jsのページコンポーネント内の関数をJestを使用してテストした際のエラー

解決したいこと

Next.jsのデータ層にfirestore 認証にFirebase Authenticationを使用したTODOアプリケーション
でJestを使用して関数の呼び出しをテストしたいです。
モック化等はうまくいってると思いますが、下記エラーが出ています。
解決方法を教えてください。

テストしたい関数
・handleLoginButton, handleLogoutButton, addTodo, editTodo, deleteTodo

発生している問題・エラー

ReferenceError: handleLoginButton is not defined

      143 | };
      144 |
    > 145 | module.exports = { handleLoginButton, handleLogoutButton, addTodo, editTodo, deleteTodo };

該当するソースコード

index.jsx(ページコンポーネント)

import {
    addDoc,
    collection,
    deleteDoc,
    doc,
    getDocs,
    setDoc,
} from "firebase/firestore";
import { useState } from "react";
import { useAuth } from "../components/AuthContext";
import { db } from "../firebase";
import { dbAdmin } from "../firebaseAdmin";

export default function Index(props) {
    const { currentUser, login, logout } = useAuth();
    const [input, setInput] = useState("");
    const [updateTodo, setUpdateTodo] = useState([{ id: "", todo: "" }]);

    const handleLoginButton = () => {
        login();
    };

    const handleLogoutButton = () => {
        logout();
    };

    const addTodo = async () => {
        await addDoc(collection(db, "todos"), { todo: input });
        setInput("");
    };

    const editTodo = async (id) => {
        await setDoc(doc(db, "todos", id), { todo: updateTodo });
        setUpdateTodo("");
    };

    const deleteTodo = async (id) => {
        await deleteDoc(doc(db, "todos", id));
    };

    

    return (
        <>
            <h2 style={{ textAlign: "center" }}>TODOアプリケーション</h2>
            {currentUser && (
                <>
                    <div style={{ textAlign: "right", marginRight: "40px" }}>
                        <h3>ログイン中</h3>
                        <button onClick={handleLogoutButton}>ログアウト</button>
                    </div>
                    <div style={{ display: "flex", justifyContent: "center" }}>
                        <input
                            value={input}
                            onChange={(e) => {
                                setInput(e.target.value);
                            }}
                        />
                        <button onClick={addTodo}>登録</button>
                    </div>
                    <h2 style={{ textAlign: "center", borderBottom: "solid" }}>
                        TODO一覧
                    </h2>
                    <ul
                        style={{
                            marginLeft: "620px",
                            display: "inline-block",
                            marginTop: "5px",
                        }}
                    >
                        {props.todos.map((todo) => {
                            return (
                                <div
                                    key={todo.id}
                                    style={{
                                        display: "flex",
                                        marginBottom: "10px",
                                    }}
                                >
                                    <li style={{ fontSize: "20px" }}>
                                        {todo.todo}
                                    </li>
                                    <input
                                        value={updateTodo.todo}
                                        style={{ marginLeft: "10px" }}
                                        onChange={(e) => {
                                            setUpdateTodo(e.target.value);
                                        }}
                                    ></input>
                                    <button
                                        onClick={() => {
                                            editTodo(todo.id);
                                        }}
                                    >
                                        更新
                                    </button>
                                    <button
                                        onClick={() => {
                                            deleteTodo(todo.id);
                                        }}
                                    >
                                        削除
                                    </button>
                                </div>
                            );
                        })}
                    </ul>
                </>
            )}
            {!currentUser && (
                <>
                    <div style={{ textAlign: "center" }}>
                        <h2>ログインしていません。</h2>
                        <button onClick={handleLoginButton}>ログイン</button>
                    </div>
                </>
            )}
        </>
    );
}

export const getServerSideProps = async () => {
    try {
        const col = dbAdmin.collection("todos");
        const snap = await col.get();
        const todos = snap.docs.map((todo) => {
            return {
                id: todo.id,
                todo: todo.data().todo,
            };
        });
        console.log(todos);
        return {
            props: { todos },
        };
    } catch (error) {
        console.log(error);
        const todos = { id: "", todo: "" };
        return {
            props: { todos },
        };
    }
};

module.exports = { handleLoginButton, handleLogoutButton, addTodo, editTodo, deleteTodo };

index.test.js

import { handleLoginButton, handleLogoutButton, addTodo, editTodo, deleteTodo } from "../pages/index";

jest.mock("firebase/firestore", () => ({
    addDoc: jest.fn(() => Promise.resolve()),
    collection: jest.fn(),
    deleteDoc: jest.fn(() => Promise.resolve()),
    doc: jest.fn(),
    setDoc: jest.fn(() => Promise.resolve())
}));

jest.mock("../components/AuthContext", () => ({
    useAuth: jest.fn(() => ({
        currentUser: {},
        login: jest.fn(),
        logout: jest.fn()
    }))
}));

jest.mock("../firebase", () => ({
    db: {}
}));

jest.mock("../firebaseAdmin", () => ({
    dbAdmin: {
        collection: jest.fn(() => ({
            get: jest.fn(() => ({
                docs: [{
                    id: "1",
                    data: jest.fn(() => ({
                        todo: "test todo"
                    }))
                }]
            }))
        }))
    }
}));

describe("Index", () => {
    it("handleLoginButton should call login", () => {
        const { handleLoginButton } = require("../pages/index");
        handleLoginButton();
        expect(useAuth().login).toHaveBeenCalled();
    });

    it("handleLogoutButton should call logout", () => {
        const { handleLogoutButton } = require("../pages/index");
        handleLogoutButton();
        expect(useAuth().logout).toHaveBeenCalled();
    });

    it("addTodo should call addDoc", async () => {
        const { addTodo } = require("../pages/index");
        await addTodo();
        expect(addDoc).toHaveBeenCalled();
    });

    it("editTodo should call setDoc", async () => {
        const { editTodo } = require("../pages/index");
        await editTodo("1");
        expect(setDoc).toHaveBeenCalled();
    });

    it("deleteTodo should call deleteDoc", async () => {
        const { deleteTodo } = require("../pages/index");
        await deleteTodo("1");
        expect(deleteDoc).toHaveBeenCalled();
    });
});

自分で試したこと

関数名の確認等は何度も行いました。
Jestについてはほぼ初なので宜しくお願いします。

0 likes

1Answer

JavaScriptのスコープを理解されていますか?

const fn = () => {
  const number = 1;
  // Success
  console.log(number);
};

// Error
console.log(number);

この状況と同じで、
Index 関数内で宣言されたhandleLoginButton などの関数を
グローバルからexportすることはできません(そんな関数があるなんて感知できないのだから)
テストしたいのであれば、Index関数内で宣言せずに、外に出してあげるのが正解です

0Like

Your answer might help someone💌