かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その37 )( Jest で jQuery を利用したモジュールのテストを書く )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その36 )( Node.js を 6.11.1 → 8.9.1 へ、npm を 4.0.5 → 5.5.1 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Javascript のテストを作成するのにテストフレームワークは何を使えばよいのか調べていたのですが、どうも最近は Jest がいいらしいです。Mocha だと他のライブラリと組み合わせて使う必要がありますが、Jest だとオールインワンなのでセットアップが楽で、これをインストールするだけでモックやコードカバレッジも利用できるようになるとのこと。
    • Jest は Facebook 製で React.js のプロジェクトのテストに一番向いているようですが、jQuery のテストを書くことも出来るようなので、試しに書いてみます。

参照したサイト・書籍

  1. Jest
    https://facebook.github.io/jest/

  2. Facebook製のJavaScriptテストツール「Jest」の逆引き使用例
    https://qiita.com/chimame/items/e97883fd46b67529d59f

  3. React + jestでテスト実行時にだけjQueryを読み込む(jquery-rails向け)
    https://qiita.com/foloinfo/items/40e04252dc5ea3638031

  4. Jestを使ってみてのハマりどころメモ
    http://lealog.hateblo.jp/entry/2017/11/16/172815

  5. DOM Manipulation
    https://facebook.github.io/jest/docs/en/tutorial-jquery.html

  6. JavaScriptのプログラミングはこれだけ効率化できる!使用歴5年目のエンジニアが送るWebStormの厳選神業集
    https://ics.media/entry/16760

    • 今回の記事とは関係ありませんが、WebStorm で Javascript を実装する際に効率化する方法がまとまった記事が出ていたのでメモしておきます。
  7. @use JSDoc
    http://usejsdoc.org/index.html

    • こちらも今回の記事とは関係ありませんが、JSDoc の Webサイトを見つけたのでメモしておきます。

目次

  1. Jest をインストールする
  2. IntelliJ IDEA の Javascript language version の設定を ECMAScript 5.1 → ECMAScript 6 に変更する
  3. 簡単なテストを作成してみる
  4. converter.js のテストを書いてみる
  5. コードカバレッジを取得する
  6. モックを定義して validator.js のテストを書いてみる

手順

Jest をインストールする

npm install --save-dev jest コマンドを実行して Jest をインストールします。

f:id:ksby:20171129011318p:plain

package.json を修正して npm test コマンドで jest が実行されるようにします。

  "scripts": {
    "test": "jest",
    ..........
  • "test": "echo \"Error: no test specified\" && exit 1""test": "jest" に変更します。

テストがない状態で npm test コマンドを実行してみると、以下の画像の結果が出力されます。

f:id:ksby:20171129015033p:plain

これを見ると、__tests__ ディレクトリ(階層はルート直下でなくてもよい)の下の ~spec.js(x) あるいは ~test.js(x) というファイルを作成しておけば、そのファイルのテストが実行されるようです。

js のソースは src/main/assets ディレクトリの下に作成したので、テストは src/test の下に asserts/__tests__ ディレクトリを作成し、その下にテストの js ファイルを作成するようにします。

f:id:ksby:20171129024108p:plain

IntelliJ IDEA の Javascript language version の設定を ECMAScript 5.1 → ECMAScript 6 に変更する

Jest のテストは Node.js の Javascript エンジン「V8」上で動かすので、ES2015 で書くことにします。

IntelliJ IDEA のメインメニューから「File」-「Settings...」を選択して「Settings」ダイアログを表示した後、左側のメニューから「Language & Frameworks」-「Javascript」を選択します。

画面右側の「Javascript language version」で「ECMAScript 6」を選択します。

f:id:ksby:20171201004143p:plain

簡単なテストを作成してみる

1つのファイルだけでテストするサンプルを作成してみます。

// 別ファイルの jQuery を利用したモジュールをテストする場合に var $ = require("jquery") だと
// エラーになるので global.$ にセットする
global.$ = require("jquery");

describe("テストのサンプルその1", () => {

    const setError = function (id) {
        $(id).addClass("has-error")
    };

    const sampleBlurEventHandler = function (event) {
        $("#sample").val("length = " + $("#sample").val().length);
    };

    beforeEach(() => {
        // HTML を書く時は ES2015 のテンプレート文字列を使うと楽
        document.body.innerHTML = `
            <div class="form-group" id="form-group-sample">
              <div class="control-label col-sm-2">
                <label class="float-label">サンプル</label>
              </div>
              <div class="col-sm-10">
                <div class="row">
                  <div class="col-sm-10">
                    <input type="text" name="sample" id="sample" class="form-control form-control-inline"
                           maxlength="20" value="" placeholder="例)サンプル"/>
                  </div>
                </div>
                <div class="row hidden js-errmsg">
                  <div class="col-sm-10">
                    <p class="form-control-static text-danger">
                      <small>ここにエラーメッセージを表示します</small>
                    </p>
                  </div>
                </div>
              </div>
            </div>
        `;
    });

    test("setError関数を呼ぶと指定されたidのelementにhas-errorクラスが追加される", () => {
        expect($("#form-group-sample").prop("class")).not.toContain("has-error");
        setError("#form-group-sample");
        expect($("#form-group-sample").prop("class")).toContain("has-error");
    });

    test("blurイベントが発生すると値が'length = [入力された文字列の長さ]'に変わる", () => {
        const str = "これはテストです。";
        $("#sample").on("blur", sampleBlurEventHandler);
        $("#sample").val(str);
        $("#sample").blur();
        expect($("#sample").val()).toBe("length = " + str.length)
    });

});

npm test コマンドを実行すると、テストが成功することが確認できます。

f:id:ksby:20171201071900p:plain

ちなみに test("setError関数を呼ぶと指定されたidのelementにhas-errorクラスが追加される", ... の方のテストが失敗するように以下のように修正してから、

    test("setError関数を呼ぶと指定されたidのelementにhas-errorクラスが追加される", () => {
        expect($("#form-group-sample").prop("class")).not.toContain("has-error");
        setError("#form-group-sample");
        // expect($("#form-group-sample").prop("class")).toContain("has-error");
        expect($("#form-group-sample").prop("class")).toContain("has-success");
    });

npm test コマンドを実行すると、テストは全て実行されますが、修正したテストが失敗することが確認できます。

f:id:ksby:20171201072122p:plain

修正したテストは元に戻します。

converter.js のテストを書いてみる

まず src/main/assets/js の下のモジュールを require で読み込めるよう設定ファイルを追加します。ルートディレクトリ直下に jest.config.json というファイルを新規作成し、以下の内容を記述します。

{
  "moduleDirectories": [
    "node_modules",
    "src/main/assets/js"
  ]
}

設定ファイルは js 形式(jest.config.js)でも書けますが、JSON 形式と js 形式では以下の違いがありました。後で(次の記事の予定)IntelliJ IDEA の Run/Debug Configurations で Jest の設定をしてテストを実行する方法をまとめるので、今回は JSON 形式にします。

  • JSON 形式
    • npm scripts で --config=... で設定ファイルを指定する必要がある。
    • IntelliJ IDEA の Run/Debug Configurations で Jest の設定をして実行することができる。
  • js 形式
    • npm scripts で --config=... で設定ファイルを指定しなくてもよい(自動で認識される)。
    • IntelliJ IDEA の Run/Debug Configurations で Jest の設定をして実行することができない(JSON形式しか指定できない)。

次に package.json を以下のように変更します。

  "scripts": {
    "test": "jest --config=jest.config.json",
    ..........
  • --config=jest.config.json を追加します。

テストを書きます。src/test/assets/tests の下に lib/util ディレクトリを作成します。

src/main/assets/tests/lib/util の下に converter.test.js を新規作成し、以下の内容を記述します。

"use strict";

global.$ = require("jquery");
const converter = require("lib/util/converter.js");

describe("converter.js のテスト", () => {

    describe("convertHiragana のテスト", () => {
        beforeEach(() => {
            document.body.innerHTML = `
              <input type="text" name="sample" id="sample" value=""/>
            `;
        });

        test("全角カタカナはひらがなに変更される", () => {
            $("#sample").val("アイウエオ");
            converter.convertHiragana(["#sample"]);
            expect($("#sample").val()).toBe("あいうえお");
        });

        test("半角カタカナはひらがなに変更される", () => {
            $("#sample").val("アイウエオ");
            converter.convertHiragana(["#sample"]);
            expect($("#sample").val()).toBe("あいうえお");
        });
    });

    describe("convertHanAlphaNumeric のテスト", () => {
        beforeEach(() => {
            document.body.innerHTML = `
              <input type="text" name="sample" id="sample" value=""/>
            `;
        });

        test("全角英数字は半角に変更される", () => {
            $("#sample").val("AZaz09");
            converter.convertHanAlphaNumeric(["#sample"]);
            expect($("#sample").val()).toBe("AZaz09");
        });
    });

});

npm test コマンドを実行するとテストが成功しました。

f:id:ksby:20171202081253p:plain

コードカバレッジを取得する

まずレポートファイルが出力するディレクトリを jest.config.json に設定します。設定しない場合、プロジェクトのルートディレクトリ直下に coverage ディレクトリが作成されて、その中に出力されます。Javacheckstyle 等のツールは /build/reports ディレクトリの下にレポートファイルを出力しているので、/build/reports/jest ディレクトリの下に出力されるようにします。

jest.config.json を以下のように変更します。

{
  "coverageDirectory": "build/reports/jest",
  "moduleDirectories": [
    "node_modules",
    "src/main/assets/js"
  ]
}
  • "coverageDirectory": "build/reports/jest" を追加します。

コードカバレッジを取得するよう npm scripts を変更します。package.json を以下のように変更します。

  "scripts": {
    "test": "jest --config=jest.config.json --coverage",
    ..........
  • --coverage を追加します。

npm test コマンドを実行してみます。今度はテストが実行されたモジュールのコードカバレッジの状況も出力されました。

f:id:ksby:20171202083043p:plain

レポートファイルを見てみます。build/reports/jest の下には以下のようなファイルが出力されています。

f:id:ksby:20171202083300p:plain

build/reports/jest/lcov-report/index.html を開いてみます。

f:id:ksby:20171202083439p:plain

converter.js がリンクになっているのでクリックしてみると、コードのどの部分が何回呼び出されたのかが分かります。

f:id:ksby:20171202083636p:plain f:id:ksby:20171202083932p:plain

このレポートファイルは見やすくていいですね。

モックを定義して validator.js のテストを書いてみる

src/test/assets/tests/lib/util の下に validator.test.js を新規作成し、以下の内容を記述します。

"use strict";

global.$ = require("jquery");
const validator = require("lib/util/validator.js");
// Form クラスをモックにする
jest.mock("lib/class/Form.js");
const Form = require("lib/class/Form.js");

describe("validator.js のテスト", () => {

    describe("checkRequired のテスト", () => {
        test("form.isAnyEmpty = false なら form.setSuccess が呼び出される", () => {
            // form.isAnyEmpty をモックメソッドにして常に false を返すようにする
            var form = new Form([]);
            form.isAnyEmpty = jest.fn().mockImplementation(() => false);

            // validator.checkRequired メソッドを呼び出す
            validator.checkRequired(form, ["#form-group-sample"], ["#sample"], "");

            // form.setSuccess メソッドが呼び出され、form.setError メソッドが呼び出されていないことを確認する
            expect(form.setSuccess).toBeCalled();
            expect(form.setError).not.toBeCalled();
        });

        test("form.isAnyEmpty = true なら form.setError が呼び出される", () => {
            // form.isAnyEmpty をモックメソッドにして常に true を返すようにする
            var form = new Form([]);
            form.isAnyEmpty = jest.fn().mockImplementation(() => true);

            // validator.checkRequired メソッドを呼び出す
            // Error オブジェクトが throw されるはず
            const errmsg = "エラーメッセージ";
            expect(() => {
                validator.checkRequired(form, ["#form-group-sample"], ["#sample"], errmsg);
            }).toThrow(new Error(errmsg));

            // form.setSuccess メソッドは呼び出されず、form.setError メソッドが呼び出されることを確認する
            expect(form.setSuccess).not.toBeCalled();
            expect(form.setError).toBeCalled();
        });
    });

});

npm test コマンドを実行します。テストは全て成功しますが、コードカバレッジが低いと赤色や黄色の文字で表示されます。

f:id:ksby:20171202161251p:plain

レポートファイルを開いてみます。build/reports/jest/lcov-report/index.html を開くと、今度はディレクトリ単位でコードカバレッジが表示され、

f:id:ksby:20171202161517p:plain

class のリンクをクリックしていくと、

f:id:ksby:20171202161627p:plain f:id:ksby:20171202161735p:plain

Form.js が表示され、実行されていない箇所が赤く表示されます。

履歴

2017/12/02
初版発行。