前書き
午前中ずっと降り続いていた雨は突然として止み、子供らが庭にわらわらと集まってきた。最近はけん玉がブーム用で、お互いが技を繰り出している。空気中のちりほこりが雨で洗い流されたためか、空気澄んでいる。平和な世界だ。
Webサービスの話
WEBサービスを引き続きつくっている。JavaScriptのテストを書いていた。入力フォームに値を入れて保存を押すと、任意のJavaScriptのオブジェクトの値が変わる。実際にはしっかりと動作していているが、これをテストするのに少し苦労した。忘れても良いように、ここに書いておく。
JavaScriptのテストツールはJestを導入した。ドキュメントも日本語のものがあり、参考になるページが多くありそうだったためだ。JestだけでなくTesting Libraryというツールも導入する。
Jest · 🃏 Delightful JavaScript Testing
Testing Library | Testing Library
コードの紹介
はじめにテストしたい関数をexportする。今回はapp/javascript/src/simulation.js
の一部である。
//略 export function setObjectparams(e, params, facilities, facilityDialog) { e.preventDefault(); let id = params.id; let name = params.name; let processingTime = params.processingTime; let selectedFacility = facilities.find((facility) => facility.id == id.value); selectedFacility.name = name.value; selectedFacility.processingTime = processingTime.value; facilityDialog.close; } // 略 export function setFacilityDataToModal(facility) { let id = document.getElementById("hidden-id"); let name = document.getElementById("name"); let processingTime = document.getElementById("processingTime"); id.value = facility.id; name.value = facility.name; processingTime.value = facility.processingTime; }
次にテストファイルを作成する。app/javascript/test/savesimulation.test.js
とした。
import { fireEvent, screen, waitFor } from "@testing-library/dom"; import "@testing-library/jest-dom"; import { setObjectparams } from "../src/simulation.js"; import { setFacilityDataToModal } from "../src/simulation.js"; let facilities; let facilitiesInitial; beforeEach(() => { //略 facilities = facilitiesInitial; }); test("button click and set params", async () => { // ここにテストを書く });
解説
import { fireEvent, screen, waitFor } from "@testing-library/dom";
で @testing-library/dom
から fireEvent
などのオブジェクトを扱えるようになる。
import { setObjectparams } from "../src/simulation.js";
でローカルのファイルからexportされた関数 setObjectparams
が使える。
beforeEach
は各テストが実行される前に処理される部分。 test(..
はテストだ。今回は非同期処理を含むので、引数の指定に非同期処理の関数 async
が入っている。
テストを書く
1. テスト用にDOMをセットアップ
実際のサービスで使うDOMでなく、テスト用のDOMを準備する。
test("button click and set params", async () => { const div = document.createElement("div"); div.innerHTML = ` <dialog id="facilityDialog"> <div id="hidden-id" hidden></div> <div> <label for="name">設備名</label> <input type="text" id="name" name="name" maxlength="16" size="15" /> </div> <div> <label for="processingTime">加工時間</label> <input type="number" id="processingTime" name="name" min="0" max="400" /> </div> <div> <button id="confirmBtn" value="default">保存</button> </div> </dialog> `; document.body.appendChild(div);
2. 値のセットと値の取得
実際の作成した関数 setFacilityDataToModal()
を使って、テスト用のDOMの入力フォームに値をセットしておく。それができたら次はDOM上から名前と加工時間を取得する。
fireEvent.change
を使うことでフォームに値を入力させることができる。
あとはボタンを押した時のイベントを追加する。
document.body.appendChild(div); setFacilityDataToModal(facilities[0]); const nameInput = screen.getByLabelText("設備名"); const processingTimeInput = screen.getByLabelText("加工時間"); fireEvent.change(nameInput, { target: { value: "Test Facility" } }); fireEvent.change(processingTimeInput, { target: { value: 30 } }); const button = screen.getByText("保存"); button.addEventListener("click", (e) => { let params = {}; params.id = document.getElementById("hidden-id"); params.name = document.getElementById("name"); params.processingTime = document.getElementById("processingTime"); let facilityDialog = document.getElementById("facilityDialog"); setObjectparams(e, params, facilities, facilityDialog); });
3. DOM操作が終わったあとに差異をみる
テストコードの中でボタンを押し、そのイベントによってDOMが操作される。(今回はモーダルウィンドウが閉じる)さらにはJavaScriptの変数の中身が変わる。それをテストする。
//上のコードのつづき await fireEvent.click(button); await waitFor(() => expect(screen.queryByText("設備名")).not.toBeVisible()); let selectedFacility = facilities.find((facility) => facility.id == "n0"); expect(selectedFacility.processingTime).toBe("30"); expect(selectedFacility.name).toBe("Test Facility");
waitFor...
を使うことで、モーダルが閉じてからそれ以降のアサーションを実行できる。モーダルウィンドウが閉じるのはDOMがなくなるわけではなく、見えなくなるだけである。だからnot.toBeVisible()
を使っている。
以上。だいぶ長くなってしまった。