Jest入門1　Matchersを組んで出力結果をテスト

テストの自動化をJavaScriptから行う方法を調査していたところ、Jestというテストフレームワークに出会いました。
試しに使用してみたところ、期待通りに動作したので、今回ご紹介したいと思います。

公式：https://jestjs.io/ja/docs/getting-started
インストール方法は以下
・npm
・Yarn
・pnpm


チュートリアルファイルを作成し、コードを実行したところ、以下の結果となりました。

試験内容: 1 + 2 は 3 という結果が返りますか？


> training@1.0.0 test
> jest

jest-haste-map: Haste module naming collision: countdown
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>\chap17\countdown\package.json
    * <rootDir>\chap17\practice\sec_end3\package.json

 PASS  ./sum.test.js
  √ adds 1 + 2 to equal 3 (4 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |
 sum.js   |     100 |      100 |     100 |     100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.067 s
Ran all test suites.

試験にパスできたので、期待値通りですね。

Jestは英語のフレームワークですが、一部日本語にも対応しています。
読みやすさ向上のために日本語を活用するのも良いでしょう。

Jestでは様々な値をテストすることができます。
値をチェックする「Matchers」と呼ばれる機能があり、
今回は試験的にMatchersを作成しました。


・describe いくつかの関連するテストを１つのブロックとしてまとめるためのメソッド。

describe("テストブロックの説明(日本語が使えます）", () => {
    //テストケースを書く//
});

test（it）実際のテストコードを記述するためのメソッド。describeメソッドの中に関連するテストケースを記述する。testの代わりにitも可。
 describe("テストブロックの説明", () => {
    test("テストケース１の説明", () => {
       //テストコードを記述//
    });
    test("テストケース２の説明", () => {
       //テストコードを記述//
    });
 });

・expect テスト結果を評価するためのメソッド。マッチャ関数とともに使用される。

 test("Xの値がAになることの確認", () => {
    expect(X).toBe(A)
 });


//1+2が3となることが期待値
   test('1+2が3になるかをテストする', () => {
   expect(1 + 2).toBe(3);
   });
   //toBeというのががMatchersとなる
 
・正常
 describe('たし算', () => {
   it('1たす3は4です', () => {
     const result = 1 + 3;
     expect(result).toBe(4);
   });
 });
・誤ったもの
 describe('ひき算', () => {
   it('2ひく1は1です', () => {
     const result = 2 - 11;
     expect(result).toBe(2);
   });
 });

・スキップ
 describe('わり算', () => {
   it.skip('3わる3は1です', () => {
     const result = 3 / 3;
     expect(result).toBe(1);
   });
 });
***
・変数も扱える
 describe('たし算', () => {
   describe('変数を使います', () => {
     let number_a = 100;
     let number_b = 100;
     // このテストファイルのすべてのテストが実行される前1回だけ実行される
     beforeAll(() => {
       number_a = number_a + 50;
     });
     // このテストファイルのすべてのテストが実行された後1回だけ実行される
     afterAll(() => {
       number_a = 100;
     });
     // このテストファイルにあるテスト(it)が実行される前に毎回実行される
     beforeEach(() => {
       number_b = number_b + 50;
     });  
     // このテストファイルにあるテスト(it)が実行された後に毎回実行される
     afterEach(() => {
       number_b = number_b - 50;
     });
     it('100に50をたすと150です', () => {
       expect(number_a).toBe(150);
     });
     it('100に50をたすと150です', () => {
       expect(number_b).toBe(150);
     });
   });
 });
***
・.not 検証を否定する
test('2+2が5にならないことをテストする', () => {
   expect(2 + 2).not.toBe(5);
 });
***
・toBeNull は null のみ一致します
 test('nullになることをテストする', () => {
   expect(null).toBeNull();
   expect(null).not.toBeNull();//nullにならないこと
 });
***
・toBeUndefined は undefined のみ一致します
test('undefinedになることをテストする', () => {
   expect(undefined).toBeUndefined();//undefined
   expect(undefined).not.toBeDefined();//undefinedではない
 });
***
・toBeDefined は toBeUndefined の反対です
 test('undefinedにならないことをテストする', () => {
   expect(1).toBeUndefined();//undefined
   expect(1).not.toBeDefined();//undefinedではない
 });
 
 //toBeTruthy は if ステートメントが真であるとみなすものに一致します
 test('trueになることをテストする', () => {
   expect(true).toBeTruthy();
   expect(1).toBeTruthy();
   expect('aaa').toBeTruthy();
 });

***
・toBeFalsy は if ステートメントが偽であるとみなすものに一致します

 test('falseになることをテストする', () => {
   expect(false).toBeFalsy();
   expect(undefined).toBeFalsy();
   expect(null).toBeFalsy();
   expect(0).toBeFalsy();
 });
***
・toBeGreaterThan(number)数値の比較する
test('2+2の計算', () => {
   const value = 2 + 2;
   expect(value).toBeGreaterThan(3);//numberより大きくなることを検証します。〇〇より大きいなので同じ値を含みません。
   expect(value).toBeGreaterThanOrEqual(3.5);//number以上になることを検証します。〇〇以上なので同じ値を含みます。
   expect(value).toBeLessThan(5);//numberより小さくなることを検証します。〇〇より小さいなので同じ値を含みません。
   expect(value).toBeLessThanOrEqual(4.5);//number以下になることを検証します。〇〇以下なので同じ値を含みます。
   expect(value).toBe(4);// toBeは数値として同じ値であるか
   expect(value).toEqual(4);//toEqualはとして同じ値であるか
 });
***
・丸め誤差が原因で期待通りにならない場合は
test('浮動小数点数の加算', () => {
   const value = 0.1 + 0.2;
   expect(value).toBe(0.3);//このように書くと、丸め込み誤差が原因で期待通りに動作しない
   expect(value).toBeCloseTo(0.3); // これならば正しく動く
 });
***
・文字列に対して正規表現でマッチするかを検証します。
test('Iはありますか？', () => {
   expect('team').not.toMatch(/I/);
 });
 test('stopは含まれているか', () => {
   expect('Christoph').toMatch(/stop/);
 });
***
・配列と反復可能なオブジェクト、　配列やオブジェクトの中にitemが含まれているかを検証するために使います。
const noodles = [
   'そば',
   'うどん',
   'ラーメン',
   'そうめん',
   'にゅうめん',
 ];
 test('そばが含まれているかをテストする', () => {
   expect(noodles).toContain('そば');
   expect(new Set(noodles)).toContain('そば');
 });
***
・ある関数が呼び出し時に例外を投げることをテストする
function compileAndroidCode() {
   throw new Error('例外が発生しました。');
 }

 test('例外が発生したかをテストする', () => {
   expect(() => compileAndroidCode()).toThrow();
   expect(() => compileAndroidCode()).toThrow(Error);
   expect(() => compileAndroidCode()).toThrow('例外が発生しました');

 });

***
上記のテストコードの結果は以下のようになりました

 FAIL  ./index.test.js
  √ 2+2が5にならないことをテストする
  × nullになることをテストする (1 ms)
  √ undefinedになることをテストする (1 ms)
  × undefinedにならないことをテストする (1 ms)
  √ trueになることをテストする
  √ falseになることをテストする (1 ms)
  √ 2+2の計算 (2 ms)
  × 浮動小数点数の加算 (1 ms)
  √ Iはありますか？ (1 ms)
  √ stopは含まれているか (1 ms)
  √ そばが含まれているかをテストする (1 ms)
  √ 例外が発生したかをテストする (4 ms)
  たし算
    √ 1たす3は4です (3 ms)
    変数を使います
      √ 100に50をたすと150です (1 ms)
      √ 100に50をたすと150です
  ひき算
    × 2ひく1は1です (3 ms)
  わり算
    ○ skipped 3わる3は1です

  ● ひき算 › 2ひく1は1です

    expect(received).toBe(expected) // Object.is equality

    Expected: 2
    Received: -9

      44 |    it('2ひく1は1です', () => {
      45 |      const result = 2 - 11;
    > 46 |      expect(result).toBe(2);
         |                     ^
      47 |    });
      48 |  });
      49 |

      at Object.toBe (index.test.js:46:21)

  ● nullになることをテストする

    expect(received).not.toBeNull()

    Received: null

       95 |  test('nullになることをテストする', () => {
       96 |    expect(null).toBeNull();
    >  97 |    expect(null).not.toBeNull();//nullにならないこと
          |                     ^
       98 |  });
       99 |
      100 |  //toBeUndefined は undefined のみ一致します

      at Object.toBeNull (index.test.js:97:21)

  ● undefinedにならないことをテストする

    expect(received).toBeUndefined()

    Received: 1

      106 |  //toBeDefined は toBeUndefined の反対です
      107 |  test('undefinedにならないことをテストする', () => {
    > 108 |    expect(1).toBeUndefined();//undefined
          |              ^
      109 |    expect(1).not.toBeDefined();//undefinedではない
      110 |  });
      111 |

      at Object.toBeUndefined (index.test.js:108:14)

  ● 浮動小数点数の加算

    expect(received).toBe(expected) // Object.is equality

    Expected: 0.3
    Received: 0.30000000000000004

      140 | test('浮動小数点数の加算', () => {
      141 |    const value = 0.1 + 0.2;
    > 142 |    expect(value).toBe(0.3);//このように書くと、丸め込み誤差が原因で期待通りに動作しない
          |                  ^
      143 |    expect(value).toBeCloseTo(0.3); // これならば正しく動く
      144 |  });
      145 |

      at Object.toBe (index.test.js:142:18)

Test Suites: 1 failed, 1 total
Tests:       4 failed, 1 skipped, 12 passed, 17 total
Snapshots:   0 total
Time:        0.889 s, estimated 1 s
Ran all test suites matching /index.test.js/i

日本語が使えることにより、成功・失敗の判別が非常に分かりやすくなりました。
Jestでは日本語が使えるので、積極的に活用していきたいです。
上記のMatchersだけでなく、公式ドキュメントには完全なリストが掲載されています。
https://jestjs.io/ja/docs/expect

現在は単に出力をテストしているだけですが、Jestには豊富な拡張機能が用意されているので、結合テストまでできるように挑戦したいですね。