2018-06-05

フロントエンドのrequireはテストを考えると注意が必要

※ 以下の話は Nuxt 1 + Webpack 3 前提です。

先日導入した Nuxt の環境で mocha-webpack でテストコードを書いている。

なんだけど、require / import 周りでどうしても伝統的な環境のクセが出てハマることが何回か起きていたのでそのメモ。

非webpack環境下でファイルを扱うサンプル

例えばファイルを扱うコードを書く際に、従来(webpack じゃない環境)はよく以下のようなコードを書いていた。

class Klass {
  /**
   * @return {String}
   */
  path() {
  }

  /**
   * @return {String}
   */
  load() {
    return <require とか read とか>(this.path())
  }
}

ファイルの扱いを気にしたくないので、ファイルの情報とその内容、データ構造などをセットで class の責務として持たせ、Klass を使う側は欲しい情報だけを適度に扱うメソッド経由で触る。

で、テスト時に path() を stub out してテスト用のファイルを食わせる。HTTP request だと request 先の URI を環境に応じて切り替えるような感じ。stub out じゃなくて外からパス情報を与えろとかあるかもしれないけど、まぁいずれにせよ「パス情報だけを独立して扱える」という前提なわけだ。

ところがこのようなコードは webpack ではいくつかの意味でダメなコードなのだ。

  1. そもそも webpack で動く require には変数も与えられない
  2. webpack の require は parse 時に即実行される

そもそもwebpackで動くrequireには変数は与えられない

これはメソッドとして分離しているかどうかではなく、以下のように動的にパスを組み立てるコードでも一緒。

const path = dir + base + param + '.js'
require(path)

これは「require が現れたらその中のパスを解釈していい具合に読み込んで JS の中に JS やデータを置く」という webpack の挙動に対して、「パスの解釈を許さない」というコードになってしまうのだ。

require の中に動的な組み立てが入っている場合は解釈できるので、どうしてもやりたい場合はそのように書くとよいが、その場合もテストには向いていない。

webpackのrequireはparse時に即実行される

プロダクトコードに require を書き、テストコードで stub out した require を書いた場合、実は require そのものが2回起きる。

まぁそらそうかという感じではあるんだけど、要は webpack を通す段階で require の登場する部分は遅延評価できない。人間の意識としてはコードが実行される前、 webpack がコードの変換を行う段階で require は実行、解決されてしまう。

まとめ

だから stub out するかどうかではなく、テスト対象のコードの require は都合よく変更できないので、以下のようにしてあげるのが正解。

  1. パス情報の解釈、require の処理は外で行い、
  2. 外からデータを与え、
  3. データの解釈だけを責務としてあげる

require ではなく HTTP になっていれば問題ないが、今回はそこまでやる必要はないので webpack の require で食わせるデータをいい具合にビルドプロセス1で生成してやろうと思っていたら、require 周りのテストで思ったように動かずに悩んでしまった、という話でした。

  1. こっちは webpack ではなく Node.js 

About

例によって個人のなんちゃらです