Flask

QUnit・Sinon.JS・jquery-mockjaxを利用してAjax呼出を行うJavaScriptメソッドの単体テストを実装してみた

QUnitはJavaScript用のTestingFrameworkで、これにSinon.JSを組み合わせると、JavaScriptのalertメソッドをMock化できる。さらに、jquery-mockjaxを組み合わせると、Ajax呼出処理をMock化できる。

今回は、QUnit・Sinon.JS・jquery-mockjaxを利用してAjax呼出を行うJavaScriptメソッドのテストを実装してみたので、そのサンプルプログラムを共有する。

前提条件

下記記事の実装が完了していること。

pytestを利用してCSVファイルを取り込みJSON形式に変換する処理の単体テストを実装してみたpytestとは、Python用に設計された単体テスト用のフレームワーク(テスティングフレームワーク)で、Flaskを用いたWebアプリ...

また、インターネットに接続できるPCを利用していること。

サンプルプログラムの作成

作成したサンプルプログラムの構成は、以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから追加したプログラムである。

「js_test_demo.js」の内容は以下の通りで、「demo.js」のチェック処理と、Ajax戻り値(JSON形式で受け取ったCSV取込結果)を画面に表形式で出力する処理のテストを実行している。

// テスト用にファイルオブジェクトを設定するためのメソッド
function set_file(file_name, file_str) {
  const file_blob = new Blob([file_str], {type: 'text/plain'});
  const file_obj = new File([file_blob], file_name, {type: "text/plain"});
  const dt = new DataTransfer();
  dt.items.add(file_obj);
  document.querySelector('input#csv_file').files = dt.files;
}

// ファイルが指定されなかった場合のテスト
QUnit.test('no file selected test', function (assert) {
  // alertメッセージをMock化する
  const alert_mock = sinon.stub(window, "alert");
  // ファイルを指定しない状態で、テスト対象メソッドを呼び出す
  import_csv();
  // alertメッセージの呼び出し回数・メッセージ文言を確認する
  assert.equal(alert_mock.callCount, 1, "message number of calls ok.");
  assert.equal(alert_mock.getCall(0).args[0], "取り込むCSVファイルを選択してください", "alert message ok.");
  // alertメッセージを初期化する
  sinon.restore();
});

// 拡張子がCSV以外のファイルが指定された場合のテスト
QUnit.test('not csv file test', function (assert) {
  // alertメッセージをMock化する
  const alert_mock = sinon.stub(window, "alert");
  // CSV以外のファイルを指定した状態で、テスト対象メソッドを呼び出す
  set_file('test.txt', 'id,name,birth_year,birth_month,birth_day,sex,memo');
  import_csv();
  // alertメッセージの呼び出し回数・メッセージ文言を確認する
  assert.equal(alert_mock.callCount, 1, "message number of calls ok.");
  assert.equal(alert_mock.getCall(0).args[0], "ファイルの拡張子がCSVファイルでありません", "alert message ok.");
  // alertメッセージを初期化する
  sinon.restore();
});

// 空のファイルが指定された場合のテスト
QUnit.test('file empty test', function (assert) {
  // alertメッセージをMock化する
  const alert_mock = sinon.stub(window, "alert");
  // 空のファイルを指定した状態で、テスト対象メソッドを呼び出す
  set_file('test.csv', '');
  import_csv();
  // alertメッセージの呼び出し回数・メッセージ文言を確認する
  assert.equal(alert_mock.callCount, 1, "message number of calls ok.");
  assert.equal(alert_mock.getCall(0).args[0], "CSVファイルが空になっています", "alert message ok.");
  // alertメッセージを初期化する
  sinon.restore();
});

// CSVファイルが指定され、正常に画面表示された場合のテスト
QUnit.test('csv file test normal', function (assert) {
  // Ajaxで呼ばれるメソッド(import-csv)の戻り値(JSON文字列)を指定
  const response_js = '[\n' +
    ' {\n' +
    '  "id": "1",\n' +
    '  "name": "テスト プリン1",\n' +
    '  "birth_year": "2012",\n' +
    '  "birth_month": "1",\n' +
    '  "birth_day": "15",\n' +
    '  "sex": "2",\n' +
    '  "memo": "テスト1,テスト"\n' +
    ' },\n' +
    ' {\n' +
    '  "id": "2",\n' +
    '  "name": "テスト プリン2",\n' +
    '  "birth_year": "2013",\n' +
    '  "birth_month": "10",\n' +
    '  "birth_day": "6",\n' +
    '  "sex": "1",\n' +
    '  "memo": "テスト2"\n' +
    ' },\n' +
    ' {\n' +
    '  "id": "3",\n' +
    '  "name": "テスト プリン3",\n' +
    '  "birth_year": "2010",\n' +
    '  "birth_month": "5",\n' +
    '  "birth_day": "8",\n' +
    '  "sex": "1",\n' +
    '  "memo": ""\n' +
    ' }\n' +
    ']'
  // Ajaxで呼ばれるメソッド(import-csv)をmock化する
  $.mockjax({
    url: '/import-csv',     // メソッド名:import-csv
    method: 'POST',       // メソッド種類:POST
    status: 200,        // レスポンス:正常
    responseText: response_js  // 戻り値:response_js
  });
  // CSVファイルのデータ
  const csv_file_str = 'id,name,birth_year,birth_month,birth_day,sex,memo\n' +
    '1,"テスト プリン1",2012,1,15,"2","テスト1,テスト"\n' +
    '2,"テスト プリン2",2013,10,6,"1","テスト2"\n' +
    '3,"テスト プリン3",2010,5,8,"1",""'
  // CSVファイルを指定した状態で、テスト対象メソッドを呼び出す
  set_file('user_data.csv', csv_file_str);
  import_csv();
  // 非同期処理の定義
  const done = assert.async();
  setTimeout(function(){
    // 初期表示時のメッセージが表示されないことを確認
    const first_msg = '~ここにCSV取込結果が表形式で出力されます~';
    assert.ok($("#show_result").text().indexOf(first_msg) < 0, "not contains first message OK");
    // 表のタイトル行・データ行を取得
    const th_obj = $('#show_result table tr th');
    assert.equal(th_obj.length, 7, "th cound OK");
    const td_obj = $('#show_result table tr td');
    assert.equal(td_obj.length, 21, "td cound OK");
    // 設定された表のタイトル行が仕様通りに設定されていることを確認
    assert.equal(th_obj.eq(0).text(), 'id', "title id OK");
    assert.equal(th_obj.eq(1).text(), 'name', "title name OK");
    assert.equal(th_obj.eq(2).text(), 'birth_year', "title birth_year OK");
    assert.equal(th_obj.eq(3).text(), 'birth_month', "title birth_month OK");
    assert.equal(th_obj.eq(4).text(), 'birth_day', "title birth_day OK");
    assert.equal(th_obj.eq(5).text(), 'sex', "title sex OK");
    assert.equal(th_obj.eq(6).text(), 'memo', "title memo OK");
    // 1行目のデータが仕様通りに設定されていることを確認
    assert.equal(td_obj.eq(0).text(), '1', "first row id OK");
    assert.equal(td_obj.eq(1).text(), 'テスト プリン1', "first row name OK");
    assert.equal(td_obj.eq(2).text(), '2012', "first row birth_year OK");
    assert.equal(td_obj.eq(3).text(), '1', "first row birth_month OK");
    assert.equal(td_obj.eq(4).text(), '15', "first row birth_day OK");
    assert.equal(td_obj.eq(5).text(), '2', "first row sex OK");
    assert.equal(td_obj.eq(6).text(), 'テスト1,テスト', "first row memo OK");
    // 2行目のデータが仕様通りに設定されていることを確認
    assert.equal(td_obj.eq(7).text(), '2', "second row id OK");
    assert.equal(td_obj.eq(8).text(), 'テスト プリン2', "second row name OK");
    assert.equal(td_obj.eq(9).text(), '2013', "second row birth_year OK");
    assert.equal(td_obj.eq(10).text(), '10', "second row birth_month OK");
    assert.equal(td_obj.eq(11).text(), '6', "second row birth_day OK");
    assert.equal(td_obj.eq(12).text(), '1', "second row sex OK");
    assert.equal(td_obj.eq(13).text(), 'テスト2', "second row memo OK");
    // 3行目のデータが仕様通りに設定されていることを確認
    assert.equal(td_obj.eq(14).text(), '3', "third row id OK");
    assert.equal(td_obj.eq(15).text(), 'テスト プリン3', "third row name OK");
    assert.equal(td_obj.eq(16).text(), '2010', "third row birth_year OK");
    assert.equal(td_obj.eq(17).text(), '5', "third row birth_month OK");
    assert.equal(td_obj.eq(18).text(), '8', "third row birth_day OK");
    assert.equal(td_obj.eq(19).text(), '1', "third row sex OK");
    assert.equal(td_obj.eq(20).text(), '', "third row memo OK");
    // Ajaxで呼ばれるメソッド(import-csv)のmockをクリアする
    $.mockjax.clear();
    // 非同期処理の呼び出し
    done();
  }, 1000);
});

// CSVファイルが指定され、Ajax呼び出しが異常終了した場合のテスト
QUnit.test('csv file test error', function (assert) {
  // Ajaxで呼ばれるメソッド(import-csv)をmock化する
  $.mockjax({
    url: '/import-csv',     // メソッド名:import-csv
    method: 'POST',       // メソッド種類:POST
    status: 400         // レスポンス:異常
  });
  // CSVファイルのデータ
  const csv_file_str = 'id,name,birth_year,birth_month,birth_day,sex,memo\n' +
    '1,"テスト プリン1",2012,1,15,"2","テスト1,テスト"\n' +
    '2,"テスト プリン2",2013,10,6,"1","テスト2"\n' +
    '3,"テスト プリン3",2010,5,8,"1",""'
  // alertメッセージをMock化する
  const alert_mock = sinon.stub(window, "alert");
  // CSVファイルを指定した状態で、テスト対象メソッドを呼び出す
  set_file('user_data.csv', csv_file_str);
  import_csv();
  // 非同期処理の定義
  const done = assert.async();
  setTimeout(function(){
    // 1秒後に結果を確認する
    // alertメッセージの呼び出し回数・メッセージ文言を確認する
    assert.equal(alert_mock.callCount, 1, "message number of calls ok.");
    assert.equal(alert_mock.getCall(0).args[0], "CSV取込に失敗しました", "alert message ok.");
    // alertメッセージを初期化する
    sinon.restore();
    // Ajaxで呼ばれるメソッド(import-csv)のmockをクリアする
    $.mockjax.clear();
    // 非同期処理の呼び出し
    done();
  }, 1000);
});
エンジニアファーストバナー

また、「js_test_demo.html」の内容は以下の通りで、「js_test_demo.js」実行するために必要なQUnit・Sinon.JS・jquery・jquery-mockjaxのインポートと、「js_test_demo.js」を呼び出して結果を画面表示している。

<!DOCTYPE html>
<html lang="ja">
<head>
  <!-- QUnitをインポート -->
  <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.24.2.css">
  <script src="https://code.jquery.com/qunit/qunit-2.24.2.js"></script>
  <!-- Sinon.JSをインポート -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/21.0.0/sinon.js"></script>
  <!-- jqueryをインポート -->
  <script src="../demo/static/jquery-3.7.1.js"></script>
  <!-- jquery-mockjaxをインポート -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mockjax/2.6.1/jquery.mockjax.js"></script>
  <!-- テスト対象となるJSファイル -->
  <script src="../demo/static/demo.js"></script>
  <!-- テストコードを記載したJSファイル -->
  <script src="js_test_demo.js"></script>
  <title>CSVファイル取込テスト</title>
</head>
<body>
  <div id="qunit"></div>
  <!-- テストのため、index.htmlのbody要素をここに記載 -->
  <div id="qunit-fixture">
    取り込んだCSVファイルのデータを、JSON形式に変換後、表形式で出力します。
    <br/><br/>
    <form enctype="multipart/form-data">
      <input type="file" id="csv_file" name="csv_file" /> 
      <input type="button" value="CSV取込" onclick="import_csv()" />
      <br/><br/>
      <div id="show_result">~ここにCSV取込結果が表形式で出力されます~</div>
    </form>
  </div>
</body>
</html>

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/python/tree/master/make-flask-csv-to-json-jstest/

Androidロックを解除する裏ワザ「4uKey for Android」をご紹介Android端末では、以下の画像のような画面ロックパスワードを設定することができますが、このパスワードを忘れてしまうと、Android...

サンプルプログラムの実行結果

サンプルプログラムの実行結果は、以下の通り。

1)「js_test_demo.html」をダブルクリックする。
サンプルプログラムの実行結果_1

2) 以下のように、「js_test_demo.html」内の「js_test_demo.js」を実行し、全て正常に処理が終了できているのが確認できる。
サンプルプログラムの実行結果_2

3) 2)で「no file selected test」を押下すると、以下のように、ファイルが指定されなかった場合のテスト(no file selected test)の、assert.equalメソッドの実行結果が確認できる。
サンプルプログラムの実行結果_3

4) 2)で「csv file test normal」を押下すると、以下のように、ファイルが指定されなかった場合のテスト(no file selected test)の、assert.equal・assert.okメソッドの実行結果が確認できる。
サンプルプログラムの実行結果_4

要点まとめ

  • QUnitはJavaScript用のTestingFrameworkで、これにSinon.JSを組み合わせると、JavaScriptのalertメソッドをMock化できる。さらに、jquery-mockjaxを組み合わせると、Ajax呼出処理をMock化できる。
  • QUnit・Sinon.JS・jquery-mockjaxを利用すると、Ajax呼出を行うJavaScriptメソッドのテストを行える。