かんがるーさんの日記

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

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その40 )( Form.js のテストを Jest で書く )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その39 )( Spring Boot を 1.5.7 → 1.5.9 へバージョンアップする ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Jest を使用して Javascript のモジュールのテストを書きます。
    • 今回は src/main/assets/js/lib/class/Form.js のテストを書きます。

参照したサイト・書籍

  1. How to duplicate object properties in another object?
    https://stackoverflow.com/questions/9362716/how-to-duplicate-object-properties-in-another-object

  2. Object.keys()
    https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

目次

  1. テストを書くファイルを用意する
  2. focus イベントに関するメソッドのテストを書く
    1. setFocused メソッド
    2. isAllFocused メソッド
    3. setFocusedFromList メソッド
    4. forceAllFocused メソッド
    5. backupFocusedState, restoreFocusedState メソッド
  3. 値の入力有無をチェックするメソッドのテストを書く
    1. isAllEmpty メソッド
    2. isAnyEmpty メソッド
    3. isAnyNotEmpty メソッド
  4. 続きます。。。

手順

テストを書くファイルを用意する

src/test/assets/tests/lib の下に class ディレクトリを作成し、その下に Form.test.js を新規作成して以下の内容を記述します。

"use strict";

global.$ = require("jquery");
const Form = require("lib/class/Form.js");

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

    describe("focus イベントに関するメソッドのテスト", () => {

        beforeEach(() => {
            document.body.innerHTML = `
              <input type="text" name="name" id="name" value=""/>
              <input type="radio" name="age" id="age_1" value="1"/>男性
              <input type="radio" name="age" id="age_2" value="2"/>女性
              <input type="checkbox" name="enquete" id="enquete_1" value="1"/>回答1
              <input type="checkbox" name="enquete" id="enquete_2" value="2"/>回答2
              <select id="job">
                <option value="1">会社員</option>
                <option value="2">学生</option>
                <option value="3">その他</option>
              </select>
            `;
        });

        const idList = [
            "#name",
            "input:radio[name='age']",
            "input:checkbox[name='enquete']",
            "#job"
        ];

        // ここにテストを書く

    });

});

focus イベントに関するメソッドのテストを書く

setFocused メソッド

以下のテストを書いて、

        test("focusイベントが発生するとfocusedプロパティにselectorがセットされる", () => {
            let form = new Form(idList);
            expect(form.focused).not.toHaveProperty("#name", true);
            expect(form.focused).not.toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).not.toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).not.toHaveProperty("#job", true);

            $("#name").focus();
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).not.toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).not.toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).not.toHaveProperty("#job", true);

            $("input:radio[name='age']").focus();
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).not.toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).not.toHaveProperty("#job", true);

            $("input:checkbox[name='enquete']").focus();
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).not.toHaveProperty("#job", true);

            $("#job").focus();
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).toHaveProperty("#job", true);
        });

実行するとテストは成功しました。

f:id:ksby:20171213011149p:plain

isAllFocused メソッド

以下のテストを書いて、

        test("isAllFocused メソッドのテスト", () => {
            const idList2 = [
                "input:radio[name='age']",
                "input:checkbox[name='enquete']"
            ];
            let form = new Form(idList);
            expect(form.isAllFocused(form, idList)).toBe(false);

            $("#name").focus();
            expect(form.isAllFocused(form, idList)).toBe(false);

            $("input:radio[name='age']").focus();
            expect(form.isAllFocused(form, idList)).toBe(false);
            expect(form.isAllFocused(form, idList2)).toBe(false);

            $("input:checkbox[name='enquete']").focus();
            expect(form.isAllFocused(form, idList)).toBe(false);
            expect(form.isAllFocused(form, idList2)).toBe(true);

            $("#job").focus();
            expect(form.isAllFocused(form, idList)).toBe(true);
        });

実行するとテストは成功しました。

f:id:ksby:20171213064213p:plain

setFocusedFromList メソッド

以下のテストを書いて、

        test("setFocusedFromList メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAllFocused(form, idList)).toBe(false);
            form.setFocusedFromList(form, idList);
            expect(form.isAllFocused(form, idList)).toBe(true);

            const idList2 = [
                "input:radio[name='age']",
                "input:checkbox[name='enquete']"
            ];
            let form2 = new Form(idList);
            expect(form2.isAllFocused(form2, idList)).toBe(false);
            form2.setFocusedFromList(form2, idList2);
            expect(form2.isAllFocused(form2, idList)).toBe(false);
            expect(form2.isAllFocused(form2, idList2)).toBe(true);
        });

実行するとテストは成功しました。

f:id:ksby:20171213065154p:plain

forceAllFocused メソッド

以下のテストを書いて、

        test("forceAllFocused メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAllFocused(form, idList)).toBe(false);
            form.forceAllFocused(form);
            expect(form.isAllFocused(form, idList)).toBe(true);
        });

実行するとテストは成功しました。

f:id:ksby:20171213065840p:plain

backupFocusedState, restoreFocusedState メソッド

以下のテストを書いて、

        test("backupFocusedState, restoreFocusedState メソッドのテスト", () => {
            let form = new Form(idList);
            form.forceAllFocused(form);
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).toHaveProperty("#job", true);
            expect(form.backupFocused).not.toHaveProperty("#name", true);
            expect(form.backupFocused).not.toHaveProperty("input:radio[name='age']", true);
            expect(form.backupFocused).not.toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.backupFocused).not.toHaveProperty("#job", true);

            form.backupFocusedState(form);
            expect(form.backupFocused).toHaveProperty("#name", true);
            expect(form.backupFocused).toHaveProperty("input:radio[name='age']", true);
            expect(form.backupFocused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.backupFocused).toHaveProperty("#job", true);

            form.focused = {};
            form.restoreFocusedState(form);
            expect(form.focused).toHaveProperty("#name", true);
            expect(form.focused).toHaveProperty("input:radio[name='age']", true);
            expect(form.focused).toHaveProperty("input:checkbox[name='enquete']", true);
            expect(form.focused).toHaveProperty("#job", true);
        });

実行するとテストが失敗しました。。。

f:id:ksby:20171213072333p:plain

いろいろ調べて How to duplicate object properties in another object?Object.keys() の記事を見つけました。オブジェクトのプロパティをコピーするのに配列のコピーと勘違いしていたからでした。。。 Form.js を以下のように変更します。

function Form(idList) {
    this.idList = idList;
    this.focused = {};
    this.backupFocused = {};
    addFocusEventListener(this);
}

..........

/**
 * form.focused の値をバックアップする
 * @param {Form} form - Form オブジェクト
 */
Form.prototype.backupFocusedState = function (form) {
    Object.keys(form.focused).forEach(function (key) {
        form.backupFocused[key] = form.focused[key];
    });
};

/**
 * form.focused の値を form.backupFocusedState を呼び出した時の状態に戻す
 * @param {Form} form - Form オブジェクト
 */
Form.prototype.restoreFocusedState = function (form) {
    Object.keys(form.backupFocused).forEach(function (key) {
        form.focused[key] = form.backupFocused[key];
    });
};
  • focused, backupFocused のプロパティの初期化を []{} に変更します。
  • backupFocusedState, restoreFocusedState メソッド内の処理を Object.keys() を使用する方法に変更します。

再度テストを実行すると今度は成功しました。

f:id:ksby:20171213231918p:plain

これまで作成したテストを全て実行しても成功しました。

f:id:ksby:20171213232032p:plain

値の入力有無をチェックするメソッドのテストを書く

isAllEmpty メソッド

以下のテストを書いて、

    describe("値の入力有無をチェックするメソッドのテストを書く", () => {

        beforeEach(() => {
            document.body.innerHTML = `
              <input type="text" name="name" id="name" value=""/>
              <input type="radio" name="age" id="age_1" value="1"/>男性
              <input type="radio" name="age" id="age_2" value="2"/>女性
              <input type="checkbox" name="enquete" id="enquete_1" value="1"/>回答1
              <input type="checkbox" name="enquete" id="enquete_2" value="2"/>回答2
              <select id="job">
                <option value="">選択してください</option>
                <option value="1">会社員</option>
                <option value="2">学生</option>
                <option value="3">その他</option>
              </select>
            `;
        });

        const idList = [
            "#name",
            "input:radio[name='age']",
            "input:checkbox[name='enquete']",
            "#job"
        ];

        test("isAllEmpty メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAllEmpty(idList)).toBe(true);

            // input[type='text']
            $("#name").val("a");
            expect(form.isAllEmpty(idList)).toBe(false);
            $("#name").val("");
            expect(form.isAllEmpty(idList)).toBe(true);

            // input[type='radio']
            $("input:radio[name='age'][value='1']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:radio[name='age'][value='1']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);
            $("input:radio[name='age'][value='2']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:radio[name='age'][value='2']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);

            // input[type='checkbox']
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAllEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAllEmpty(idList)).toBe(true);

            // select
            $("#job").val("1");
            expect(form.isAllEmpty(idList)).toBe(false);
            $("#job").val("2");
            expect(form.isAllEmpty(idList)).toBe(false);
            $("#job").val("3");
            expect(form.isAllEmpty(idList)).toBe(false);
            $("#job").val("");
            expect(form.isAllEmpty(idList)).toBe(true);
        });

    });

実行するとテストが失敗しました。最初の expect(form.isAllEmpty(idList)).toBe(true); で失敗していますが、checkbox の処理を実装していないので、当然ですね。。。

f:id:ksby:20171215003536p:plain

Form.js を以下のように変更します。

/**
 * idList で渡された id の要素が全て未入力/未選択かチェックする
 * @param {Array} idList - チェックする id がセットされた配列
 * @returns {boolean} true:全て未入力/未選択である, false:1つ以上入力/選択されているものがある
 */
Form.prototype.isAllEmpty = function (idList) {
    var allEmpty = true;
    idList.forEach(function (id) {
        if ($(id).attr("type") === "radio") {
            if ($(id + ":checked").val() !== undefined) {
                allEmpty = false;
            }
        } else if ($(id).attr("type") === "checkbox") {
            if ($(id + ":checked").length > 0) {
                allEmpty = false;
            }
        } else if ($(id).val() !== "") {
            allEmpty = false;
        }
    });
    return allEmpty;
};
  • else if ($(id).attr("type") === "checkbox") { ... } の処理を追加します。

再度テストを実行すると今度は成功しました。

f:id:ksby:20171215011024p:plain

isAnyEmpty メソッド

isAnyEmpty メソッドも checkbox の実装をしていないので、テストの前に修正します。Form.js を以下のように変更します。

/**
 * idList で渡された id の要素の中に未入力/未選択のものがあるかチェックする
 * @param {Array} idList - チェックする id がセットされた配列
 * @returns {boolean} true:未入力/未選択のものがある, false:全て入力/選択されている
 */
Form.prototype.isAnyEmpty = function (idList) {
    var anyEmpty = false;
    idList.forEach(function (id) {
        if ($(id).attr("type") === "radio") {
            if ($(id + ":checked").val() === undefined) {
                anyEmpty = true;
            }
        } else if ($(id).attr("type") === "checkbox") {
            if ($(id + ":checked").length === 0) {
                anyEmpty = true;
            }
        } else if ($(id).val() === "") {
            anyEmpty = true;
        }
    });
    return anyEmpty;
};
  • else if ($(id).attr("type") === "checkbox") { ... } を追加します。

以下のテストを書いて、

        test("isAnyEmpty メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAnyEmpty(idList)).toBe(true);

            $("#name").val("a");
            $("input:radio[name='age'][value='1']").prop("checked", true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            $("#job").val("1");
            expect(form.isAnyEmpty(idList)).toBe(false);

            $("#name").val("");
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("#name").val("a");
            expect(form.isAnyEmpty(idList)).toBe(false);

            $("input:radio[name='age'][value='1']").prop("checked", false);
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("input:radio[name='age'][value='1']").prop("checked", true);
            expect(form.isAnyEmpty(idList)).toBe(false);

            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            expect(form.isAnyEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAnyEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            expect(form.isAnyEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);

            $("#job").val("");
            expect(form.isAnyEmpty(idList)).toBe(true);
            $("#job").val("1");
            expect(form.isAnyEmpty(idList)).toBe(false);
        });

実行するとテストは成功しました。

f:id:ksby:20171216071456p:plain

isAnyNotEmpty メソッド

isAnyNotEmpty メソッドも checkbox の実装をしていないので、テストの前に修正します。Form.js を以下のように変更します。

/**
 * idList で渡された id の要素の中に1つでも入力/選択されているものがあるかチェックする
 * @param {Array} idList - チェックする id がセットされた配列
 * @returns {boolean} true:1つでも入力/選択されている, false:全て未入力/未選択である
 */
Form.prototype.isAnyNotEmpty = function (idList) {
    var anyNotEmpty = false;
    idList.forEach(function (id) {
        if ($(id).attr("type") === "radio") {
            if ($(id + ":checked").val() !== undefined) {
                console.log(id + ", " + $(id + ":checked").val());
                anyNotEmpty = true;
            }
        } else if ($(id).attr("type") === "checkbox") {
            if ($(id + ":checked").length > 0) {
                console.log(id + ", " + $(id + ":checked").length);
                anyNotEmpty = true;
            }
        } else if ($(id).val() !== "") {
            console.log(id + ", " + $(id).val());
            anyNotEmpty = true;
        }
    });
    return anyNotEmpty;
};
  • else if ($(id).attr("type") === "radio") { ... } を追加します。

以下のテストを書いて、

        test("isAnyNotEmpty メソッドのテスト", () => {
            let form = new Form(idList);
            expect(form.isAnyNotEmpty(idList)).toBe(false);

            // input[type='text']
            $("#name").val("a");
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("#name").val("");
            expect(form.isAnyNotEmpty(idList)).toBe(false);

            $("input:radio[name='age'][value='1']").prop("checked", true);
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("input:radio[name='age'][value='1']").prop("checked", false);
            expect(form.isAnyNotEmpty(idList)).toBe(false);

            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            expect(form.isAnyNotEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAnyNotEmpty(idList)).toBe(false);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", true);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", true);
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("input:checkbox[name='enquete'][value='1']").prop("checked", false);
            $("input:checkbox[name='enquete'][value='2']").prop("checked", false);
            expect(form.isAnyNotEmpty(idList)).toBe(false);

            $("#job").val("1");
            expect(form.isAnyNotEmpty(idList)).toBe(true);
            $("#job").val("");
            expect(form.isAnyNotEmpty(idList)).toBe(false);
        });

実行するとテストは成功しました。

f:id:ksby:20171216184029p:plain

ここまで作成したテストが全て成功することを確認します。

f:id:ksby:20171216185101p:plain

続きます。。。

Jest + IntelliJ IDEA を組み合わると jQuery を利用した Javascript のモジュールのテストが書きやすいです。document.body.innerHTML に必要最低限の HTML だけ記述して動作確認が出来るのが便利ですね。

IntelliJ IDEA を 2017.3.1 へバージョンアップしてから、Form.js のテストを続けます。

履歴

2017/12/16
初版発行。