ちょっっとだけJestをつかってみた

こんにちはこんにちは。きょうはちょっとだけJavaScriptのテスティングフレームワークJestをお試しでつかってみました話をば。

趣味開発なので、がっつりテストプログラムを書くつもりはないのですが、それでも重要な部分(データを更新したり、読み込んだりなど)はテストがあったほうが安心できていいなーと思ったのでした。 サーバ側ではRSpecを使ってテストを書いているのですが、JavaScript はどうしようかなーと思っていたので、今回はJestを素振りしてみることにしました。

インストール

インストールは基本的に公式のチュートリアル通りにやればよさそうなので、以下のページを参考にインストールしてみます。

facebook.github.io

まずはインストール。プロジェクトディレクトリで以下を実行します。

npm install --save-dev jest

適当にテストを書き(書いたテストについては後述)、テストを npm test で実行しようとしたら怒られてしまいました。

 This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
 By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

ES201xな記述だと、どうやらそのままではテストできないみたい(import で構文エラーになってしまった)なので、babelをインストールします。

 npm install  --save-dev babel-jest babel-core regenerator-runtime
 npm install babel-preset-env --save-dev

あとは、.babelrcを以下のように設定します

// .babelrc
{
  "presets": ["env"]
}

package.jsonに以下を追加します

"scripts": {
    "test": "jest"
}

あとは npm test を実行してあげれば、テストを実行することができました。インストールはとても簡単でした。

簡単なテストを書いてみる

いまつくっているプログラムのテストを部分的に書いてみます。以下のプログラムは、与えられた文字列が意図した数値であるかを確認するためのものです。 (開発中なので、このプログラムの良し悪しについては目をつぶっていただきたく...)

具体的には、次のような確認をします。

  • 文字列が数値かどうか
  • 整数値であることが条件の場合は整数値であるかどうか
  • 最大値が設定されている場合は最大値以下か
  • 最小値が設定されている場合は最小値以上か

関数は、意図した数値であれば数値自体を戻り値として返し、意図せぬ数値であれば例外をスローします。

// validateHelper.js

import * as Errors from './errors.js'

export default class ValidationHelper {

...

static isValidNumber(num, param = {}) {
  if( (typeof param.defaultValue !== 'undefined') && (typeof num === 'undefined') ) {
    return param.defaultValue;
  }

  if( param.isInt ) {
    if( !(/(^-?\d+$)/g.test(String(num))) ) throw new Errors.WrongIntValidationError('the string is including characters');
  } else {
    if( !(/(^-?\d+(\.\d+)?$)/g.test(String(num))) ) throw new Errors.WrongNumberValidationError('the string is including characters');
  }

  if( typeof param.maxValue !== "undefined" ) {
    if( num > param.maxValue ) throw new Errors.WrongNumberValidationError('given number is over than maximum');
  }

  if( typeof param.minValue !== "undefined" ) {
    if( num < param.minValue ) throw new Errors.WrongNumberValidationError('given number is less than minimum');
  }

  return Number(num);
}

こんな感じに使うことを想定しています。

// 与えられた文字列が数値かどうか
let hoge = Validate.isValidNumber(fuga);
// 与えられた数値が整数値でかつ、10以下か
let hoge = Validate.isValidNumber(fuga, {isInt: true, maxValue: 10});

とりあえず、まずは「文字列が数値である」という判定が正しく行われるかを確認するテストを書いてみます。describeでテスト条件によって適切にグループ化し、test内で確認をします。 (後で知ったのですが、ittestエイリアスとして使えるみたいです(参考)。個人的にはitのほうが好きだなーと思ったのでした。)

今回テストしたい関数は「意図した数値であれば数値自体を戻り値として返す」仕様になっているので、数値が与えられたときに正しく数値が返されるかどうかを確認します。

テストのMatcher(判定器という感じかな?)は公式ドキュメントを見るのがよさそうなので、都度確認します。値が同一かどうかを確認するには toEqualが使えそうなので、 これを使います。書いたテストは以下の通り。

import ValidateHelper from './validateHelper.js' 

describe('when number is given', () => {
  test('return number', () => {
    // 数値を渡したら数値が返される
    expect(ValidateHelper.isValidNumber(1234)).toEqual(1234);
  });

  test('return number when type of string', () => {
    // 文字列型でも数値が返される
    expect(ValidateHelper.isValidNumber('1234')).toEqual(1234);
  });

  test('return number when the given number is less than zero', () => {
    // 負号が付いていても数値が返される
    expect(ValidateHelper.isValidNumber('-1234')).toEqual(-1234);
  });

  test('return number when the given number includes a decimal point', () => {
    // 小数点があっても数値が返される
    expect(ValidateHelper.isValidNumber('12.34')).toEqual(12.34);
  });
});

つぎに、「最大値が設定されている場合は最大値を超えたら例外をスロー」が正しく動くかを確認してみます。書いたテストは以下の通り。

import ValidateHelper from './validateHelper.js' 

describe('maximum value is given', () => {
  test('throw error when given number is over than maximum value', () => {
    expect(() => ValidateHelper.isValidNumber('12', {maxValue: 10})).toThrow(/maximum/);
  });

  test('return number when given number is less than maximum value', () => {
    expect(ValidateHelper.isValidNumber(1, {maxValue: 2})).toEqual(1);
  });
});

「最大値が与えられた場合、最大値より大きな値が与えられた場合は例外を発生する」「最大値以下の場合は数値を返す」が仕様なので、それらを確認できるように書きます。 例外が発生することを確認するには toThrow をつかいます。expect で例外を捕捉するには、functionでラップする必要があることに注意です。

ちなみに、toThrow には例外クラスを引数に取ることができるとドキュメントには記載されているのですが、なぜかうまくいかず。。。 どうやら以下のリンク先と同様の事象なのかもという感じですが、具体的な解決策はもうちょっと調べないとわからないかなという感じです。

toThrowError not working with custom errors and Babel · Issue #2123 · facebook/jest · GitHub

ひとまずエラーメッセージを引数に取ることもできるようなので、今回はエラーメッセージを確認するテストとしました。

あとは同じようにテストを書いていきます。最終的に書いたテストはこちら。

// validateHelper.test.js

import ValidateHelper from './validateHelper.js' 

describe('#isNumber', () => {
  describe('optional parameter is not given', () => {
    describe('when number is given', () => {
      test('return number', () => {
        expect(ValidateHelper.isValidNumber(1234)).toEqual(1234);
      });

      test('return number when type of string', () => {
        expect(ValidateHelper.isValidNumber('1234')).toEqual(1234);
      });

      test('return number when the given number is less than zero', () => {
        expect(ValidateHelper.isValidNumber('-1234')).toEqual(-1234);
      });

      test('return number when the given number includes a decimal point', () => {
        expect(ValidateHelper.isValidNumber('12.34')).toEqual(12.34);
      });
    });

    describe('when the string includes invalid characters', () => {
      test('throw error', () => {
        expect(() => ValidateHelper.isValidNumber('123hoge')).toThrow(/including characters/);
      });
    });
  });

  describe('allow only integer', () => {
    test('throw error when given number includes a decimal point', () => {
      expect(() => ValidateHelper.isValidNumber('123.45', {isInt: true})).toThrow(/including characters/);
    });

    test('return number', () => {
      expect(ValidateHelper.isValidNumber(1234)).toEqual(1234);
    });
  });

  describe('maximum value is given', () => {
    test('throw error when given number is over than maximum value', () => {
      expect(() => ValidateHelper.isValidNumber('12', {maxValue: 10})).toThrow(/maximum/);
    });

    test('return number', () => {
      expect(ValidateHelper.isValidNumber(1, {maxValue: 2})).toEqual(1);
    });
  });

  describe('minimum value is given', () => {
    test('throw error when given number is over than maximum value', () => {
      expect(() => ValidateHelper.isValidNumber('0', {minValue: 1})).toThrow(/minimum/);
    });

    test('return number', () => {
      expect(ValidateHelper.isValidNumber(10, {minValue: 1})).toEqual(10);
    });
  });
});

つかってみて

インストールが簡単でよいなと思いました。とりあえず今作っているものでテストしやすそうなものを引っ張ってきて書いてみたので、 今度はモックとかスタブとかを使いそうなものを使って書いてみようかなという気持ちです。 また、Webpackとの組み合わせとか、Vue.jsのテストを書くには?みたいなところをクリアできればよさそうなので、引き続きお試ししてみようかなと思っています。

github.com

なにか進捗があれば書いていこうと思っております。ではでは。