Node.jsをv18にアップデート後、ユニットテストにMockServiceWorkerを使用していた場合の対応策
はじめに
今年2023年9月11日にNode.jsバージョン16はEOL(LTSサポート期間終了日)を迎えます。
これに伴い、現在v16またはそれ以前のバージョンなどを使用している場合は次のLTSバージョンであるv18などにアップデートする作業が生じます。
筆者が現在参画中のプロジェクトでもv16からv18へのアップデート作業が最近実施されました。
本記事ではこちらのバージョンアップ作業に伴って発生した現象と対応策について共有させていただきます。
Node.jsリリーススケジュール
Node.jsは毎年4月と10月の2回、新しいメジャーバージョンがリリースされます。
4月に新しくリリースされるメジャーバージョンはLTS(Long Term Support)版と呼ばれる、致命的なバグの修正などを長期間に渡ってサポートするバージョンとなります。
一方で毎年10月にリリースされるメジャーバージョンは比較的サポート期間が短命であり、ここ数年の事例を見ると翌年の6月1日にEOLを迎えています。
※引用元:https://github.com/nodejs/release#release-schedule
Node.js公式ではLTS期間に入ったバージョン(ACTIVE LTSもしくはMaintenance LTS)の使用が推奨されています。
現時点(2023年2月)で、EOLを迎えていないLTS版はv14、v16、v18の3つ存在します。
v18は2025年の4月30日
までのサポートが予定されています。
一方でv18より過去にリリースされたv14系のEOLは2023年4月30日
、v16は先述の通り2023年9月11日
までとなっております。
従いまして、アプリケーションにv16以下のバージョンを現在使用している場合、現時点(2023年2月)でのアップデート先としてはv18が主な候補となります。
Fetch API
Fetch APIは元々はブラウザ上で(ネットワーク越しの通信を含む)リソース取得を実現するインターフェイスでした。
言い換えるとブラウザ上でのみ動作する為、Node.js上で実行することは出来きませんでした。そのため、Node.jsで実行するには、whatwg-fetchのようなpolyfillを使用する必要がありました。
しかしながらv17以降では、Node.js上でもpolyfill不要でFetch APIが利用可能になっています。
// 簡易な使用例
fetch("https://pokeapi.co/api/v2/pokemon/ditto").then(...);
名前解決
v16以前では、Node.jsは名前解決の際にIpv4を優先してマッピングしていました。
例えば筆者のmacOS環境/etc/hosts
は以下の通りで、localhost
はIPv4である127.0.0.1
が解決される仕様になっていました。
127.0.0.1 localhost
255.255.255.255 broadcasthost
::1 localhost
しかしv17以降ではIPv4優先が廃止され、実行環境の設定に依存する仕様となりました。
筆者のmacOS環境で試してみると、localhost
はIPv6である::1
に解決されます。
// v16
% node -e "const dns = require('dns'); dns.lookup('localhost', (err, addresses) => {console.log('addresses: %j', addresses)});"
addresses: "127.0.0.1"
// v18
% node -e "const dns = require('dns'); dns.lookup('localhost', (err, addresses) => {console.log('addresses: %j', addresses)});"
addresses: "::1"
アップデート時に発生した問題
筆者が現在参画中のプロジェクトではテストフレームワークにVitest、モックサーバにMock Service Worker(以下msw)を使用しています。
またユニットテストコード内にFetch APIのpolyfillとしてwhatwg-fetch
を使用しています。
v16では成功していたmswをモックサーバとして利用したHTTPリクエストを含むユニットテストが、アップデート後のv18では失敗する事象が発生しました。
TypeError: fetch failed
❯ Object.fetch node:internal/deps/undici/undici:11118:11
❯ process.processTicksAndRejections node:internal/process/task_queues:95:5
❯ fetchData src/hooks/useFetch.ts:76:18
75|
76| const res = await fetch(URL, {
| ^
Caused by: Error: connect ECONNREFUSED ::1:3000
❯ connect ECONNREFUSED ::1:3000
❯ TCPConnectWrap.afterConnect [as oncomplete] node:net:1300:16
こちらのissueに記載されている通り、先述した名前解決の仕様変更によってmswライブラリ内で生じた不具合がテスト失敗の原因となります。
(※ブラウザ上でアプリケーションを実行する際のモックサーバとしての利用であればv18であっても問題なくmswを使用出来ます。)
対応法
Vitestのバージョンを最新(2022/02/14時点で0.28.5)にアップデートした上で、テスト実行コマンドに--no-experimental-fetchオプションを付与することで、暫定的に問題を回避いたしました。
"scripts": {
"test": "NODE_OPTIONS='--no-experimental-fetch' vitest",
}
こちらのオプションを付与することで、Fetch APIに関してのみv18にアップデートされる以前の状態を再現させています。
厳密にはv18のFetch APIの動作をユニットテストで担保することは出来ていない状態ではありますが、Fetch APIを呼び出す前後のコード範囲についてはテストできている状態になるため、暫定対応として良しとしました。
ただしこれはあくまでも暫定策のため、今後も先ほどのissueを確認しておく必要があります。
最後に
Node.jsに限った話ではございませんが、依存しているライブラリなどのメジャーバージョンアップデートを実施いたしますと、今回ご紹介したように何かしらの動作不良がしばしば発生します。
本記事が、何かのお役に立てば幸いに存じます。