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

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

Recent Posts

Categories

Tool 日々 Web Biz Net Apple MS ことば News Unix howto Food PHP Movie Edu Community Book Security Text TV Perl Ruby Music Pdoc 生き方 RDoc ViewCVS CVS Rsync Disk Mail FreeBSD Cygwin PDF Photo Zebedee Debian OSX Comic Cron Sysadmin Font Analog iCal Sunbird DNS Linux Wiki Emacs Thunderbird Sitecopy Terminal Drawing tDiary AppleScript Life Money Omni PukiWiki Xen XREA Zsh Screen CASL Firefox Fink zsh haXe Ecmascript PATH_INFO SQLite PEAR Lighttpd FastCGI Subversion au prototype.js jsUnit Apache Trac Template Java Rhino Mochikit Feed Bloglines CSS del.icio.us SBS qwikWeb gettext Ajax JSDoc Rails HTML CHM EPWING NDTP EB IE CLI ck ThinkPad Toy WSH RFC readline rlwrap ImageMagick epeg Frenzy sysprep Ubuntu MeCab DTP ERD DBMS eclipse Eclipse Awk RD Diigo XAMPP RubyGems PHPDoc iCab DOM YAML Camino Geekmonkey w3m Scheme Gauche Lisp JSAN Google VMware DSL SLAX Safari Markdown Textile IRC Jabber Fastladder MacPorts LLSpirit CPAN Mozilla Twitter OpenFL Rswatch ITS NTP GUI Pragger Yapra XML Mobile Git Study JSON VirtualBox Samba Pear Growl Mercurial Rack Capistrano Rake Win RSS Mechanize Sitemaps Android JavaScript Python RTM OOo iPod Yahoo Unicode Github iTunes God SBM friendfeed Friendfeed HokuUn Sinatra TDD Test Project Evernote iPad Geohash Location Map Search Simplenote Image WebKit RSpec Phone CSV WiMAX USB Chrome RubyKaigi RubyKaigi2011 Space CoffeeScript Nokogiri Hpricot Rubygems jQuery Node GTD CI UX Design VCS Kanazawa.rb Kindle Amazon Agile Vagrant Chef Windows Composer Dotenv PaaS Itamae SaaS Docker Swagger Grape WebAPI Microservices OmniAuth HTTP 分析基盤 CDN Terraform IaaS HCL Webpack Vue.js BigQuery Middleman CMS AWS PNG Laravel Selenium OAuth OpenAPI GitHub UML GCP TypeScript SQL Hanami Document SVG AsciiDoc Pandoc DocBook Develop Jekyll macOS Node.js Vite Heroku Transformer AI Data Cloud Wasm