Spring Boot + npm + Geb で入力フォームを作ってテストする ( その37 )( Jest で jQuery を利用したモジュールのテストを書く )
概要
記事一覧はこちらです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
Facebook製のJavaScriptテストツール「Jest」の逆引き使用例
https://qiita.com/chimame/items/e97883fd46b67529d59fReact + jestでテスト実行時にだけjQueryを読み込む(jquery-rails向け)
https://qiita.com/foloinfo/items/40e04252dc5ea3638031Jestを使ってみてのハマりどころメモ
http://lealog.hateblo.jp/entry/2017/11/16/172815DOM Manipulation
https://facebook.github.io/jest/docs/en/tutorial-jquery.htmlJavaScriptのプログラミングはこれだけ効率化できる!使用歴5年目のエンジニアが送るWebStormの厳選神業集
https://ics.media/entry/16760- 今回の記事とは関係ありませんが、WebStorm で Javascript を実装する際に効率化する方法がまとまった記事が出ていたのでメモしておきます。
@use JSDoc
http://usejsdoc.org/index.html- こちらも今回の記事とは関係ありませんが、JSDoc の Webサイトを見つけたのでメモしておきます。
目次
- Jest をインストールする
- IntelliJ IDEA の Javascript language version の設定を ECMAScript 5.1 → ECMAScript 6 に変更する
- 簡単なテストを作成してみる
- converter.js のテストを書いてみる
- コードカバレッジを取得する
- モックを定義して validator.js のテストを書いてみる
手順
Jest をインストールする
npm install --save-dev jest
コマンドを実行して Jest をインストールします。
package.json を修正して npm test
コマンドで jest が実行されるようにします。
"scripts": { "test": "jest", ..........
"test": "echo \"Error: no test specified\" && exit 1"
→"test": "jest"
に変更します。
テストがない状態で npm test
コマンドを実行してみると、以下の画像の結果が出力されます。
これを見ると、__tests__
ディレクトリ(階層はルート直下でなくてもよい)の下の ~spec.js(x) あるいは ~test.js(x) というファイルを作成しておけば、そのファイルのテストが実行されるようです。
js のソースは src/main/assets ディレクトリの下に作成したので、テストは src/test の下に asserts/__tests__
ディレクトリを作成し、その下にテストの js ファイルを作成するようにします。
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」を選択します。
簡単なテストを作成してみる
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
コマンドを実行すると、テストが成功することが確認できます。
ちなみに 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
コマンドを実行すると、テストは全て実行されますが、修正したテストが失敗することが確認できます。
修正したテストは元に戻します。
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 の設定をして実行することができる。
- npm scripts で
- js 形式
次に 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
コマンドを実行するとテストが成功しました。
コードカバレッジを取得する
まずレポートファイルが出力するディレクトリを jest.config.json に設定します。設定しない場合、プロジェクトのルートディレクトリ直下に coverage ディレクトリが作成されて、その中に出力されます。Java の checkstyle 等のツールは /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
コマンドを実行してみます。今度はテストが実行されたモジュールのコードカバレッジの状況も出力されました。
レポートファイルを見てみます。build/reports/jest の下には以下のようなファイルが出力されています。
build/reports/jest/lcov-report/index.html を開いてみます。
converter.js がリンクになっているのでクリックしてみると、コードのどの部分が何回呼び出されたのかが分かります。
このレポートファイルは見やすくていいですね。
モックを定義して 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
コマンドを実行します。テストは全て成功しますが、コードカバレッジが低いと赤色や黄色の文字で表示されます。
レポートファイルを開いてみます。build/reports/jest/lcov-report/index.html を開くと、今度はディレクトリ単位でコードカバレッジが表示され、
class
のリンクをクリックしていくと、
Form.js が表示され、実行されていない箇所が赤く表示されます。
履歴
2017/12/02
初版発行。