Help us understand the problem. What is going on with this article?

jscodeshiftのテストを書く

(用意していた記事が壮大になってきて、本日中に書き終わらないと思ったので、急遽極小ネタに切り替えました。)

前日、同僚の@toshi__tomaがjscodeshiftについて投稿しているのですが、TDD的にAST変換を書けるのがjscodeshiftの強みの一つかなと思ったので、jscodeshiftのテストについて書きます。@toshi__tomaの記事↓と合わせて読んでいただけると、ちょうどよいかな〜と思います。

JavaScriptのリファクタリングツール「jscodeshift」の使い方 - Qiita

準備

jscodeshiftとjestをインストールします。

$ npm i -D jscodeshift jest

何も変換しないtransformer.jsを用意します。

// transformer.js
// 変換せず、そのままソースコードを返すだけ
module.exports = function (fileInfo) {
  return fileInfo.source;
}

テストを書く

「書く」と言ってもとても簡単で、変換前のコードと変換後に期待されるコードを用意し、テストファイルでそれらを読み込む関数を実行するだけです。

テストデータの用意

まず、変換前後のコードを置く__testfixtures__ディレクトリを作成します。

$ mkdir __testfixtures__

変換前後のコードを適当に作ります。変換前のコードは.input.js、変換後に期待されるコードは.output.jsの拡張子を付けます。「何も変換しない」ので、まずは全く同じコードを置いてみましょう。

$ echo "console.log('hoge');" > __testfixtures__/hoge.input.js
$ cp __testfixtures__/hoge.input.js __testfixtures__/hoge.output.js

テストファイルの設置

テストファイルを__tests__/以下に置きます。jestは、__tests__以下にテストファイルを置く方法以外に、テストファイルの拡張子を.test.js.spec.jsにする方法もありますが、変換処理が記述されたJSファイル(今回の場合、transformer.js)や__testfixtures__が、テストファイルの一つ上の階層にあることを前提に処理するので、テストファイルは__tests__以下に置きましょう。

$ mkdir __tests__
$ vim __tests__/transformer.js

テストケースの定義

jscodeshift/dist/testUtilsに定義されているdefineTest関数を使うと、テストケースを定義できます。defineTest関数の内部でjestのdescribe関数やit関数を使ってテストケースを追加してくれるので、テストファイルにはdefineTest関数以外に記述する必要はありません。

defineTest関数の引数は以下のとおりです。

  • 第1引数: テストファイルのディレクトリパスなので常に__dirnameを渡す
  • 第2引数: テスト対象の変換処理が記述されたJSファイル名(拡張子不要)
  • 第3引数: 変換処理JSに渡すオプション。無ければnull
  • 第4引数: テストデータのプリフィックス。__testfixtures__/第4引数.(input|output).jsがテストデータに利用される
// __tests__/transformer.js
const { defineTest } = require('jscodeshift/dist/testUtils');

// transformer.jsに対して、
// __testfixtures__/hoge.(input|output).jsを使ってテストする
defineTest(__dirname, 'transformer', null, 'hoge');

ちなみに、defineTest関数の第4引数を省略するとテストデータのプリフィックスとして第2引数が利用されます。

// 第4引数の省略
// transformer.jsに対して、
// __testfixtures__/transformer.(input|output).jsを使ってテストする
defineTest(__dirname, 'transformer');

では、テストを実行してみましょう。何も変換していないので、PASSします。

$ npx jest
 PASS  __tests__/transformer.js
  transformer
    ✓ transforms correctly using "hoge" data (157ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.814s, estimated 1s
Ran all test suites.

console.log('hoge');console.log('fuga');に変換するように修正してみましょう。__testfixtures__/hoge.output.jsconsole.log('fuga');に書き換え、テストを再実行します。ちゃんと、FAILしてますね。

# 期待する結果を変更
$ echo "console.log('fuga');" > __testfixtures__/hoge.output.js

# テストを再実行
$ npx jest
 FAIL  __tests__/transformer.js
  transformer
    ✕ transforms correctly using "hoge" data (54ms)

  ● transformer › transforms correctly using "hoge" data

    expect(received).toEqual(expected) // deep equality

    Expected: "console.log('fuga');"
    Received: "console.log('hoge');"

      at runInlineTest (node_modules/jscodeshift/dist/testUtils.js:36:33)
      at runTest (node_modules/jscodeshift/dist/testUtils.js:73:3)
      at Object.<anonymous> (node_modules/jscodeshift/dist/testUtils.js:90:7)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.712s, estimated 1s
Ran all test suites.

テストが通るようにtransformer.jsを修正します。

// transformer.js
module.exports = (fileInfo, { jscodeshift: j }) => {
  const root = j(fileInfo.source);

  root.find(j.Literal, { value: "hoge" }).forEach(path => {
    path.replace(j.literal("fuga"));
  });

  return root.toSource({ quote: "single" });
};

テストを再実行すると、PASSします!

$ npx jest
 PASS  __tests__/transformer.js
  transformer
    ✓ transforms correctly using "hoge" data (113ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.276s
Ran all test suites.

このように、.output.jsを修正→テストが通るまで実装を繰り返すことで、進捗を確認しながら着実に進めることができます。

defineTest関数以外にも、jscodeshift/dist/testUtilsには以下のような関数が定義されています。お好みで使い分けましょう。

  • defineInlineTest関数
    • テストデータを直接文字列で渡す版
  • defineSnapshotTest関数
    • jestのtoMatchSnapshotを使って、回帰テストを書くことができる
  • applyTransform関数
    • 変換を実行する関数。入力を引数に渡し、出力を返り値で受け取る

おわり

jscodeshiftのテストの書き方を紹介しました。jscodeshiftは、ASTの入門としておすすめですので、興味のある方はぜひ触ってみましょう〜

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away