なになれ

IT系のことを記録していきます。

Jestを使ってJavaScriptで快適にテストする

Jestとは

JavaScriptのテストフレームワークです。
フロントエンド向けのテストフレームワークで注目されました。
ただJest自体はフロントエンドに限定されずに有用なテストフレームワークです。

jestjs.io

Jestのメリット

  • テストを書き始めるまでが簡単
  • Matcherが揃っている
  • Mockが簡単に用意できる

これらのメリットについて具体的な実装を交えて説明します。

テストの書き方

テストを書く準備

Jestをインストールする

npm install --save-dev jest

テストファイルを書く

const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

これで始められます。
toBeなどのテストの検証に必要なMatcherが最初から用意されています。
テストの記述の仕方はJasmineやMochaといったテストフレームワークと同様です。

Matcherを使う

toBe,toEqualのMatcherを覚えておけば大抵のことはカバーできると思われます。

FizzBuzzの結果を返すcreateFizzBuzzListメソッドで動作確認をします。

FizzBuzz.js

module.exports = class FizzBuzz {
  static createFizzBuzzList(num) {
    const list = [];
    for (let i = 1; i <= num; i++) {
      if (i % 15 === 0) {
        list.push("FizzBuzz");
      } else if (i % 3 === 0) {
        list.push("Fizz");
      } else if (i % 5 === 0) {
        list.push("Buzz");
      } else {
        list.push(i.toString());
      }
    }
    return list;
  }
};

toBeで等価であることを検証できます。

FizzBuzz.spec.js

test("FizzBuzz test", () => {
  const actual = FizzBuzz.createFizzBuzzList(16);
  expect(actual.length).toBe(16);
  expect(actual[0]).toBe("1");
  expect(actual[1]).toBe("2");
  expect(actual[2]).toBe("Fizz");
  expect(actual[3]).toBe("4");
  expect(actual[4]).toBe("Buzz");
  expect(actual[5]).toBe("Fizz");
  expect(actual[6]).toBe("7");
  expect(actual[7]).toBe("8");
  expect(actual[8]).toBe("Fizz");
  expect(actual[9]).toBe("Buzz");
  expect(actual[10]).toBe("11");
  expect(actual[11]).toBe("Fizz");
  expect(actual[12]).toBe("13");
  expect(actual[13]).toBe("14");
  expect(actual[14]).toBe("FizzBuzz");
  expect(actual[15]).toBe("16");
});

toEqualでオブジェクトや配列の値を検証できます。

FizzBuzz.spec.js

test("FizzBuzz test custom", () => {
  const expected = [
    "1",
    "2",
    "Fizz",
    "4",
    "Buzz",
    "Fizz",
    "7",
    "8",
    "Fizz",
    "Buzz",
    "11",
    "Fizz",
    "13",
    "14",
    "FizzBuzz",
    "16"
  ];
  const actual = FizzBuzz.createFizzBuzzList(16);
  expect(actual.length).toBe(16);
  expect(actual).toEqual(expected);
});

Snapshotテストを使う

実行結果を保存するのに有用なSnapshotテストという機能があります。
Matcherのテストとは違い、期待値となる値を定義する必要がなくなるのでテストコードがシンプルになります。

FizzBuzz.spec.js

test("FizzBuzz snapshot test", () => {
  const actual = FizzBuzz.createFizzBuzzList(16);
  expect(actual).toMatchSnapshot();
});

通常のMatcherとの使い分けとしては、Snapshotテストはプロダクションコードの結果が正しいことが前提です。
正しいと判断するためのテストを書いて、そのテストを通すためのプロダクションコードを書くといったTDDのようなアプローチは取れなくなります。
実行結果を保持することで壊れていないかを確認するのがテストの目的となることもあるため、Snapshotテストは有用な手段だと思います。

Mockを使う

Jestでは自動モック機能というものがあり、簡単にMock作成が可能です。

AccountDao.js

module.exports = class AccountDao {
  findOrNull(userId) {
    return userId;
  }
};

Authentication.js

module.exports = class Authentication {
  getDao() {
    return this.dao;
  }

  setDao(val) {
    this.dao = val;
  }

  authenticate(userId, password) {
    const account = this.dao.findOrNull(userId);
    if (account === null) return null;
    return account.password === password ? account : null;
  }
};

Authentication.spec.js

const Authentication = require("../src/Authentication");
const AccountDao = require("../src/AccountDao");

jest.mock("../src/AccountDao");

describe("Authentication", () => {
  test("not exist account", () => {
    const sut = new Authentication();
    const dao = new AccountDao();
    dao.findOrNull.mockReturnValue(null);
    sut.setDao(dao);
    expect(sut.authenticate("user001", "pw123")).toBeNull();
  });
});

jest.mock関数でモジュールを全てMock化したオブジェクトを作成することができます。
これにより、new AccountDao()で作成したオブジェクトは全てMockになります。
daoオブジェクトのfindOrNullメソッドはMockになり、mockReturnValue関数などのMock用の関数を実行することが可能になります。

まとめ

テストを書くときにMockを使うのが前提となることが多いと思います。個人的には自動でMock化してくれる機能が強力です。
Jestで快適なテストライフを!

参考資料

Jestで書いたテストコード集です
github.com