かんがるーさんの日記

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

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その95 )( eslint を 6.8.0 → 7.32.0 へ、jest を 26.0.1 → 27.2.4 へバージョンアップする )

概要

記事一覧はこちらです。

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

  • 今回の手順で確認できるのは以下の内容です。
    • eslint を 6.8.0 → 7.32.0 へ、jest を 26.0.1 → 27.2.4 へバージョンアップします。

参照したサイト・書籍

  1. package.json に記載されているパッケージのバージョンアップ方法 【 npm-check-updates, outdated 】
    https://qiita.com/sugurutakahashi12345/items/df736ddaf65c244e1b4f

  2. Testing Asynchronous Code
    https://jestjs.io/docs/asynchronous

  3. axios - Cancellation
    https://github.com/axios/axios#cancellation

  4. Node.js v15に実装されたAbortController
    https://www.mitsue.co.jp/knowledge/blog/frontend/202012/14_0900.html

目次

  1. eslint を 6.8.0 → 7.32.0 へ、eslint-config-airbnb-base を 14.1.0 → 14.2.1 へ、eslint-config-prettier を 6.11.0 → 8.3.0 へ、eslint-plugin-import を 2.20.1 → 2.24.2 へ、eslint-plugin-prettier を 3.1.3 → 4.0.0 へバージョンアップする
  2. jest を 26.0.1 → 27.2.4 へバージョンアップする
  3. テストが失敗した原因を解消する
    1. The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string. Consider using the "jsdom" test environment.
    2. jQuery requires a window with a document
    3. Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise.
    4. Jest has detected the following 2 open handles potentially keeping Jest from exiting:
    5. jest.setTimeout(...); を呼び出しても効かないようなので jest.config.json に testTimeout を設定する方法に切り替える

手順

eslint を 6.8.0 → 7.32.0 へ、eslint-config-airbnb-base を 14.1.0 → 14.2.1 へ、eslint-config-prettier を 6.11.0 → 8.3.0 へ、eslint-plugin-import を 2.20.1 → 2.24.2 へ、eslint-plugin-prettier を 3.1.3 → 4.0.0 へバージョンアップする

以下のコマンドを実行して eslint 関連のモジュールを最新のバージョンにします。

  • npm install --save-dev eslint@7.32.0
  • npm install --save-dev eslint-config-airbnb-base@14.2.1
  • npm install --save-dev eslint-config-prettier@8.3.0
  • npm install --save-dev eslint-plugin-import@2.24.2
  • npm install --save-dev eslint-plugin-prettier@4.0.0

npm test コマンドを実行するとテストは全て成功し、

f:id:ksby:20211001224700p:plain

npm run build コマンドも正常に終了しました。

f:id:ksby:20211001224838p:plain

途中で npx browserslist@latest --update-db のコマンドを実行するようメッセージが出力されているので、実行します。npm update は eslint 以外のモジュールもバージョンアップされるので(しかも必ずしも最新バージョンになる訳ではないらしい)実行しません。

f:id:ksby:20211001225203p:plain f:id:ksby:20211001225244p:plain

jest を 26.0.1 → 27.2.4 へバージョンアップする

以下のコマンドを実行します。

  • npm install --save-dev jest@27.2.4
  • npm install --save-dev jest-html-reporter@3.4.1

レポートファイルを削除したいので最初に gradle から clean タスクを実行しておきます。

npm test コマンドを実行するとテストが大量に失敗するようになりました。。。

f:id:ksby:20211001233149p:plain

build/reports/jest/jest-html-reporter.html にレポートが出力されていますが、failed の赤ばかりです。

f:id:ksby:20211002000004p:plain

エラーになったテストで出力されているメッセージは全部で4つあり、1つ目は The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string. Consider using the "jsdom" test environment.

f:id:ksby:20211001234806p:plain

2つ目は jQuery requires a window with a document

f:id:ksby:20211001235051p:plain

3つ目は Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise.

f:id:ksby:20211001235235p:plain

4つ目は Jest has detected the following 2 open handles potentially keeping Jest from exiting:

f:id:ksby:20211001235442p:plain

テストが失敗した原因を解消する

The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string. Consider using the "jsdom" test environment.

エラーメッセージに出力されている https://jestjs.io/docs/configuration#testenvironment-string を見ると The test environment that will be used for testing. The default environment in Jest is a Node.js environment. If you are building a web app, you can use a browser-like environment through jsdom instead. という記述があり、テストファイルの先頭に @jest-environment docblock を追加して @jest-environment jsdom を記述すれば解決するようですが、さすがに各テストファイルに記述するのは避けたいところです。

jest.config.json"testEnvironment": "jsdom", を追加することにします。

{
  "coverageDirectory": "build/reports/jest",
  "moduleDirectories": [
    "node_modules",
    "src/main/assets/js"
  ],
  "testEnvironment": "jsdom",
  "reporters": [
    "default",
    [
      "./node_modules/jest-html-reporter",
      {
        "pageTitle": "Test Report"
      }
    ]
  ]
}

また Jest 27: New Defaults for Jest, 2021 editionFor this reason, we are changing the default test environment from "jsdom" to "node". という記述がありました。For this reason の前の文章を読むと jsdom はパフォーマンス・オーバーヘッドがあるので default を node に変更したとのこと。今後は必要なテストだけテストファイルの先頭に @jest-environment docblock を追加する方式にした方が良いのでしょう。

jQuery requires a window with a document

testEnvironment を jsdom に変更したら出力されなくなりました。

Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise.

https://jestjs.io/blog/2020/05/05/jest-26#other-breaking-changes-in-jest-26[jest-circus] Fail tests if a test takes a done callback and have return value という記述がありました。

done callback を使っているとテストが失敗するようになったことは分かりましたが、これだけでは対応方法が分かりません。他に参考になるものがないか調べてみると Done.fail function is not working の中に Testing Asynchronous Code へのリンクがありました。このページに対応方法が記載されています。

リンク先の記述に従って修正すると以下のコードの場合には、

  test("Nock で 500 を返すと catch の方で処理される", async (done) => {
    nock("http://api.openweathermap.org")
      .get(/^\/data\/2\.5\/weather/)
      .reply(500);

    await openWeatherMapHelper
      .getCurrentWeatherDataByCityName("Tokyo")
      .then((response) => {
        done.fail("then に処理が来たらエラー");
      })
      .catch((e) => {
        expect(e.response.status).toBe(500);
      });

    // test("...", async done => { ...}); と done を記述している場合には、テストの最後で done(); を呼び出さないと
    // 5秒待機した後、
    // Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
    // のエラーメッセージを表示してテストが失敗する
    // ※https://facebook.github.io/jest/docs/en/asynchronous.html#callbacks 参照
    done();
  });

async/await を取り除いて Promise を返すようにするか(Promise を返せば Jest は Promise が完了するまで待つとのこと)、

  test("Nock で 500 を返すと catch の方で処理される", () => {
    expect.assertions(1);

    nock("http://api.openweathermap.org")
      .get(/^\/data\/2\.5\/weather/)
      .reply(500);

    return openWeatherMapHelper
      .getCurrentWeatherDataByCityName("Tokyo")
      .catch(e => expect(e.response.status).toBe(500));
  });

async/await を残すならば try...catch で以下のように記述すればテストが成功するようになります。

  test("Nock で 500 を返すと catch の方で処理される", async () => {
    expect.assertions(1);

    nock("http://api.openweathermap.org")
      .get(/^\/data\/2\.5\/weather/)
      .reply(500);

    try {
      await openWeatherMapHelper.getCurrentWeatherDataByCityName("Tokyo");
    } catch (e) {
      expect(e.response.status).toBe(500);
    }
  });

今回は後者の async/await を残して try...catch で対応することにします。

  • async (done)async () に変更する。
  • done(); あるいは done.~ を呼び出している箇所を削除する。
  • テストの最初に expect.assertions(1); を記述する(assertions メソッドに渡す数字はテストで expect が呼び出される回数を指定する)。https://jestjs.io/docs/expect#expectassertionsnumber を読むと async を付けたテストメソッドの場合、きちんとテストが実行されているのかを確認するために expect.assertions を書いた方が良いらしい。
  • await を付けて呼び出しているところを、await を取り除いて try...catch で囲む記述に変更する。

Jest has detected the following 2 open handles potentially keeping Jest from exiting:

package.json で jest を実行するコマンドに --detectOpenHandles オプションを付けているのでテスト終了時にオープンされたままのハンドラが検知・出力されるのですが、以前から付けていて Jest 27 にバージョンアップされたらこれまで検知されていなかったものが検知されるようになったようです。

  "scripts": {
    "test": "run-s prettier:format prettier:format-test jest",
    "jest": "jest --config=jest.config.json --coverage --runInBand --detectOpenHandles",
    ..........

axios のリクエストを cancel する方法が https://github.com/axios/axios#cancellation に記述されており、これを使えば解消しそうな気がするのですが、うまく実装できません。。。 テストは成功しているので、今回は特に何もしないことにします。

また https://jestjs.io/docs/cli#--detectopenhandles によると --detectOpenHandlesデバッグ時のみ使用するよう書かれていたので、テストが並列で実行されなくなる --runInBand オプションと合わせて外すことにします。

  "scripts": {
    "test": "run-s prettier:format prettier:format-test jest",
    "jest": "jest --config=jest.config.json --coverage",
    "jest:debug": "jest --config=jest.config.json --coverage --runInBand --detectOpenHandles",
    ..........

jest.setTimeout(...); を呼び出しても効かないようなので jest.config.json に testTimeout を設定する方法に切り替える

上記4つの調査でいろいろ試している時に気づいたのですが、テストメソッド内で jest.setTimeout(30000); を呼び出しても 5秒経過するとテストがエラーになりました。jest.config.json"testTimeout": 30000, を設定すれば有効になるようなので、こちらの設定に切り替えます。

{
  "coverageDirectory": "build/reports/jest",
  "moduleDirectories": [
    "node_modules",
    "src/main/assets/js"
  ],
  "testEnvironment": "jsdom",
  "testTimeout": 30000,
  "reporters": [
    "default",
    [
      "./node_modules/jest-html-reporter",
      {
        "pageTitle": "Test Report"
      }
    ]
  ]
}

npm test コマンドを実行すると A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. というメッセージが出力されますが、テストは全て成功するようになりました。

f:id:ksby:20211003182726p:plain

build/reports/jest/jest-html-reporter.html のレポートも passed の緑のみになっています。

f:id:ksby:20211003183047p:plain

履歴

2021/10/03
初版発行。