かべブログ

TypeScript, React, Goなどでまったり個人開発。たまに競プロ。

Jest 素振り

はじめに

こんにちは、かべです。フロントエンド開発をする時、好き勝手書かずにちょっとずつ業務っぽいこともやってみようということで今回は Jest でテストを書いていきます。スナップショットとかE2Eとかはまた別の機会に。今回は axios を使う処理に対する簡単な単体テストを書きます。

環境

今回の開発は以下の環境で行いました。

  • Ubuntu 20.04.1
  • yarn 1.22.5
  • React 17.0.2
  • TypeScript 4.1.2

React アプリ作成

create-react-app は作成時に Jest を使う環境をすでに整えてくれるので、それに乗っかってやっていきます。npx create-react-app {好きな名前} --template typescript でプロジェクトを作成します。始めから /src 上に App.tsx に対するテストがありますが、今回は使わないので消してしまいます。

プロジェクトが出来たら、必要なものをインストールします。yarn add axiosyarn add -D axios-mock-adapter を実行し、axios とそのモックが使えるようにします。

テスト対象のコード

axios を使って外部にリクエストを送る関数を作成します。今回リクエストの送り先には、cat-facts を使います。リクエストを送るとネコの豆知識が返ってきます。かわいい。

/src 以下に /api というディレクトリを作り、axios を使って次のようなファイルを作ります。

/src/api/api.ts

import axios from "axios";

const baseUrl = "https://cat-fact.herokuapp.com";
export const axiosInstance = axios.create({
  baseURL: baseUrl,
});

export const getCatFact = async (id: string) => {
  try {
    const response = await axiosInstance.get(`/facts/${id}`);

    if (response.status !== 200) {
      throw new Error("server error");
    }

    const fact = response.data;

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

getCatFact という関数は https://cat-fact.herokuapp.com/facts/${id} にリクエストを送ってその中のデータを取得します。

テストに使う期待される返却値を準備しておきます。これは axios のモックが使用する値です。/src/api/ 内に __mocks__ というディレクトリを作り、cat-facts のリクエストに対する返却例を json で保存します。

/src/api/__mocks__/fact.json

{
  "_id": "591f98803b90f7150a19c229",
  "__v": 0,
  "text": "In an average year, cat owners in the United States spend over $2 billion on cat food.",
  "updatedAt": "2018-01-04T01:10:54.673Z",
  "deleted": false,
  "source": "api"
}

テストコード

テストコードを書いていきます。まずは正常系です。describe でテストたちをブロック化し、it(test) で各テストを書くことが出来ます。今回は取得した値の中の text が期待されたものかどうかを確認しています。expect に not を付けると、逆の内容であることをテスト出来ます。

/src/api/api.spec.ts

import MockAdapter from "axios-mock-adapter";

import { axiosInstance, getCatFact } from "./api";
import factData from "./__mocks__/fact.json";

describe("test cat-facts", () => {
  const mock = new MockAdapter(axiosInstance);

  // 使用毎にモック内のデータをクリア
  afterEach(() => {
    mock.reset();
  });

  describe("get cat-facts by id", () => {
    // 正常系
    it("should succeed", async () => {
      const id = "591f98803b90f7150a19c229";
      mock.onGet(`/facts/${id}`).reply(200, factData);

      const catFact = await getCatFact(id);

      expect(catFact.text).toBe(factData.text);
      expect(catFact.text).not.toBe("Dog is the best.");
    });
  });
});

各ブロックでモックをリセットすることを忘れずに行いましょう。これをしないと次のテストでも同じデータが使われてしまいます。

次に異常系です。存在しない ID にリクエストを送るとエラーが発生し、そのステータスコードが 404 であることを確認しています。

/src/api/api.spec.ts

/* 省略 */

describe("test cat-facts", () => {
  /* 省略 */

  describe("get cat-facts by id", () => {
    /* 省略 */

    // 異常系
    it("should return error because it uses wrong id", async () => {
      const id = "591f98803b90f7150a19c221"; // 最後の1文字が違う
      mock
        .onGet(`/facts/${id}`)
        .reply(404, JSON.parse(`{"message":"Fact not found"}`));

      try {
        await getCatFact(id);
      } catch (error) {
        expect(error.response.status).toBe(404);
      }
    });
  });
});

おまけに Jest の機能をいくつか使ってみます。arrayContaining は得られた配列が想定される配列を包含している時に通ります。浮動小数点を扱うときは toBe で比較をすると誤差で通らないため、toBeCloseTo で比較しましょう。第 2 引数は精度を表しています。

/src/api/api.spec.ts

/* 省略 */

describe("test without cat-facts", () => {
  it("contains expected array", () => {
    const expected = ["cat", "dog"];

    expect(["cat", "bird", "dog"]).toEqual(expect.arrayContaining(expected));
  });

  it("has expected property", () => {
    const cat = {
      name: "tama",
      age: 2,
      weight: 2 + 1.1,
    };

    expect(cat.weight).toBeCloseTo(3.1, 5);
  });
});

テストの実行

テストが書けたので実行してみましょう。yarn test を実行すると次のような画面になり、テストが通ります。

f:id:okb_okb:20210511115126p:plain
テスト結果

終わりに

今回は TypeScript の簡単な単体テストを行いました。フロントエンド開発での全体的なテストもいずれ行いたいと思います。

今回のコードはこちらに載せています。

参考