質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.21%

関数コンポーネントをmock化してunitテストを行いたい

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 170

kobaryo04ysh

score 22

React+TypeScript+firebaseで作成しているアプリのユニットテストを行おうとしているのですが、テストしようとしているコンポーネントがFirebaseと何らかのやりとりをしている場合に、テストがうまくいかずFirebaseErrorとなります。

テストについていろいろと調べていたところ、APIなどの外部との通信を行うアプリをテストする場合、コンポーネントをモック化してテストコードの中にどの様なデータが帰ってくるかなどを予め記述しておいてテストを行うというのが良さそうだと気づきました。

しかしながらいろいろと調べても、自分の理解力が乏しいのか、うまくいく様なテストコードを記述することができなかったので、質問させていただきます。

実現したいこと

firebaseとの通信をするハンドラを含む関数コンポーネント内で様々なテストを行える様にしたい

質問

① API通信などを含むreactの関数コンポーネントをテストするときはどの様にmockすればいいのか

② そもそもmockするという考え方は間違っていて、別のやり方があるのかどうか

ソースコード

以下はLogin.tsxです。

export default function Login() {
  const classes = useStyles();
  const emailRef = useRef();
  const passwordRef = useRef();
  const { login }: any = useAuth();
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const history = useHistory();

  const inputEmail = useCallback(
    (event) => {
      setEmail(event.target.value);
    },
    [setEmail]
  );

  const inputPassword = useCallback(
    (event) => {
      setPassword(event.target.value);
    },
    [setPassword]
  );

  async function handleSubmit(e: any) {
    e.preventDefault();

    setError("");
    setLoading(true);
    return login(email, password)
      .then(() => {
        history.push("/");
      })
      .catch((error: any) => {
        setError("failed!!");
      })
      .finally(() => {
        setLoading(false);
      });
  }

  const onClickGuestButton = () => {
    setError("");
    setLoading(true);
    return login("guest@example.com", "password")
      .then(() => {
        history.push("/");
      })
      .catch((error: any) => {
        setError("failed!!");
      })
      .finally(() => {
        setLoading(false);
      });
  };

  return(
  {/*少し長いので省略しています。*/}
  )

以下はLogin.test.tsxです。

import * as React from "react";
import Login from "../Login";
import { configure, mount, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
// import { Login as mockLogin } from "../Login";
import ReactDOM from "react-dom";

configure({ adapter: new Adapter() });

jest.mock("../Login", () => {
  return login("fake@example.com", "password"){
    return Promise.resolve(fakeUser)
  }
});

describe("Login", () => {

  it("should return currect user", () => {
    // const wrapper = shallow(<Login />);
    // expect(wrapper.find("button").length).toBe(2);
    const fakeUser = {
      username: "FakeMan",
      email: "fake@example.com",
      uid: "KN8qXkuYmPf2i9kvzC2mQylZQPo1",
    };
    jest.spyOn(global, "fetch").mockImplementation((): any =>
      Promise.resolve({
        json: () => Promise.resolve(fakeUser),
      })
    );
  });
});

jest.mock()でコンポーネントや関数などをモック化することができそうというのはわかったのですが、今回の場合どの様にすればいいのかわかりません。
また、Firebaseとの通信を行わないコンポーネントでテストを実行してみると、(当たり前ですが)FirebaseErrorは起こらず、普通にテストがpassしました。

このことから、Firebaseとの通信がテストに何らか影響しているのは確かだと思っています。
[Jest+TypeScript] クラスと関数のモック化 を参考にしてみましたが、結局よくわからず...

何かいい解決策はありますでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

0

コードを見やすくするため改めて。

まず jest.spyOn() というのは「特定」のオブジェクトに対してモックの差し替えを行います。オブジェクト A で使うあるモジュールから import してきた関数 f と オブジェクト B で使う同じモジュールから import してきた関数 f がある場合、jest.spyOn() にオブジェクト A とモック用の関数 g を渡すとオブジェクト A の関数は g になりますが、オブジェクト B の関数は f のままです。

それに対し jest.mock() は指定したモジュールをすべてモックで上書きします(なので今気づいたんですが jest.mock("../Login") としてしまうと Login コンポーネント自体がモックされてしまいますね)。いまモックにしたいのは react-use-auth だと思うので、

jest.mock('react-use-auth')


とすればよいでしょう。もしモックに値や関数を返してもらいたければ「jest hooks mock」などで検索するとよさそうな記事が出てくるので参考にすると良いと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

jest.spyOn(global, "fetch") というのは global (ブラウザーで言うと window)オブジェクトにある fetch という名前の関数が呼び出された時にその処理を横取りして処理を返しますよ、というのを意味しています。つまり fetch でデータを取得していないとモックの処理には差し替わりません。

話は変わってウェブ上からデータを取得する関数というかオブジェクトは fetch の他に  XMLHttpRequest があります。以前は後者しかなかったのですが、使いづらいという経緯があったため前者が生まれました。

さて、実際にデータを取得してる部分はどこかといいますと useAuth() あたりかなと検討をつけてソースコードを読みに行ってきました。そうしたら案の定データの取得に fetch ではなく XMLHttpRequest を使っているライブラリが使われていました。

ではこのコードにどうやってどこにモックを差し込んでテスト可能にするかなんですが、それはずばり useAuth() そのものです。そこに何も処理をしないモックを差し込んで、useAuth() が正しく呼ばれたか確認すればテストは完了です。そもそもモックは正しいデータを返すとかはしなくてよく、ただ正しく呼ばれることを意識すればそれで OK なのです(値を返してくれるのはスタブというらしい)。

ただそうは言っても useAuth() が呼ばれて正しいデータを受け取ったと仮定してその結果が正しいかテストしたい欲求もあるかもしれません。やりたければ useAuth() のソースコードを読みに行って完璧なスタブを作って差し替えるのも面白いかもしれません。ですが実際問題としてそこまでテストをやる意味があるかと言われると間違いなくないです。

useAuth は幸いにしてテストがなされているようですのでそこは信頼してしまって、他の自分で書いたコードのぶんのテストを書くと良いんじゃあないんでしょうか。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/12/12 13:14

    回答ありがとうございます!説明不足でしたが、回答いただいた通り、contextを使ってuseAuthで、context内にあるfirebaseとやりとりする関数をimportして使っています。もしよかったら、具体的なコードを提示していただけないでしょうか?お願いします!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.21%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問