プログラミングマガジン

プログラミングを中心にIT技術をできるだけわかりやすくまとめます。

  • ホーム
  • JavaScript
  • 【JavaScript】「jest」、モック化など
 
 
     
  • サーバー言語  
    • Python
    • Ruby
    • PHP
    • SQL
  •  
  • インフラ  
       
    • AWS
    •  
    • 基本
    • Git
  • Web
       
    • Web開発
    • JavaScript
    • Vue.js
    • React
  •  
  • 設計  
       
    • 実装設計
    • DB設計
  • 問い合わせ
  

【JavaScript】「jest」、モック化など

06.12

  • miyabisan2
  • コメントを書く

この記事は6分で読めます

jestとは?

  • 近年最もポピュラーなJavaScriptテストフレームワークです。
  • facebookが作っておりReactにも対応しています。
  • 元々はFacebookの社内のみで使われいましたが2013年にオープンソース化されました。

Jestの特徴

ゼロコンフィグ

細かい設定を行うことなく動作する。

スナップショット

大きなオブジェクトの追跡するテストが簡単に書ける。複雑なオブジェクトやUIの状態をテストするのは基本難しいがこの機能を使えば簡単にテストできる。

一度スナップショットを取得すれば以降の変更を追跡、確認ができる。

独立的

独立して複数のテストを並列実行して、パフォーマンスを向上できる。

優れたAPI

テストツールがAPIとして整理されている。

注意点

ES6には対応していないのでbabelと一緒に導入する必要がある。

実際のブラウザでのテストを用いたテストはできない。

jsdomというnode上で動作する仮想的なブラウザを利用することでブラウザ環境をシミュレートしますが、実際のブラウザ上でのテストはサポートしていません。なので特定のブラウザでのみ発生するようなバグの検知はできないです。

他のフレームワークとの比較

2018年ごろまではこれらのフレームワークとGithubスター数は大差なかったが、2018年以降からJestが急上昇しており、これらのフレームワークの2倍程度のスター数になっている。

Mocha

  • テストフレームワークとしては最小限の機能を提供
  • その他ライブラリと組み合わせることで柔軟な設定が可能だが、設定が複雑になることが多い。

jasmine

  • Angularの標準テストフレームワーク
  • 基本機能な備わっているが、Jestに比べて一部高度な機能(スナップショット機能など)が備わっていない。
  • 実行速度が遅い。

インストール

typescriptの場合

1
npm i -D jest @types/jest ts-jest

typescriptのコードをテストする場合はts-jestというパッケージが必要です。

設定

typescriptの場合

1
npx ts-jest config:init

上記コマンドを実行すると「jest.config.js」というファイルが自動生成されます。

jest.config.js

1
2
3
4
5
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

preset

一連の設定をまとめたものです。ts-jestであればTypeScriptをテストするための一連の設定が含まれています。

testEnvironment

Jestがどの環境でテストを実行するかを指定します。nodeが指定されていればnode-js環境でテストを実行するという意味になります。

テストコードを配置場所

「__tests__」というディレクトリにテストコードを配置することを推奨しています。

テスト実行(テストランナーとしての機能)

全てのテストを実行

package.jsonのscriptに定義しておくのが普通です。

1
jest

or

1
npm test

特定のファイルを指定して実行(個別実行)

1
jest ファイルのフルパス

ファイル名を指定して実行

フルパスを指定する場合と違い、ファイル名に合致するファイルがあれば全て実行します。

1
jest ファイル名

テスト結果

Test Suites

成功したテストファイルの数を示しています。

Tests

テスト関数もしくはitの数の合計を示しています。

SnapShots

Jestのスナップショットテストを使った合計数です。

Time

テスト全体の実行時間を示しています。

テストケースを追加する機能

1
test case

アプリケーションの期待値と結果を判定する機能

1
expect

テストコードのデバッグ方法

テストコード内にconsole.logを埋め込んでテストを実行すればコンソール上にテストコード内の変数が出力されます。

1
console.log(変数);

セットアップと破棄

describeを入れ子にして入れ子になったなかにそれぞれ仕込むことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
describe("outer", () => {
  beforeEach(() => {
    console.log("outer beforeEach");
  });
 
  afterEach(() => {
    console.log("outer afterEach");
  });
 
  beforeAll(() => {
    console.log("outer beforeAll");
  });
 
  afterAll(() => {
    console.log("outer afterAll");
  });
 
  it("outer test 1", () => {
    console.log("outer test 1");
  });
 
 
  describe("inner", () => {
    beforeEach(() => {
      console.log("inner beforeEach");
    });
 
    afterEach(() => {
      console.log("inner afterEach");
    });
 
    beforeAll(() => {
      console.log("inner beforeAll");
    });
 
    afterAll(() => {
      console.log("inner afterAll");
    });
 
  // このitではbeforeEachは外側と内側で二回実行される。
    it("inner test 1", () => {
      console.log("inner test 1");
    });
 
  });
});
 

beforeEach

各テスト(it)の前に実行される。なお、describeを入れ子にした場合はこれだけは内側のdescribeの際も外側のdescribeの内容も二重に実行されることになるので注意です。

afterEach

各テスト(it)の後に実行される。

beforeAll

ファイル内の全てのテストの前に1度だけ実行される。

afterEach

ファイル内の全てのテストの後に1度だけ実行される。

モックとスパイの違い

モックとスパイの違いは以下になります。ただ、Jestではモックとスパイの機能が一部オーバーラップしています。

モック

  • テストの事前段階で模倣する振る舞いを定義する。
  • テスト実行時に実際の振る舞いとモックの振る舞いを置き換える。

スパイ

スパイとは、既存の関数やメソッドの呼び出しを「監視」するもの。スパイの対象が「いつ・何回・どんな引数で」呼び出されたかを検証できる。

  • 実際の振る舞いを監視します。
  • テスト実行時には実際の振る舞いを置き換えることもできますが、置き換えないです。

モック化

1
2
const mockFunc = jest.fn();
expect(mockFunc("あ")).toBe(undefined);

ちなみに、モック関数(jest.fn)の戻り値は一律でundefinedになるので上記の実行結果はOKになります。

モック関数の行わせたい処理内容を設定する。

1
jest.fn(() => "い");

もしくは

1
jest.fn().mockImplementation(() => "い");

モック関数の処理内容を変更できます。(デフォルトはundefinedのため)

mockImplementationを使うことで後から実装を変更することができるというメリットがあります。

モック関数の戻り値を変更する。

mockReturnValue

何度実行しても同じ値を返します。

1
2
3
const mockFunc = jest.fn().mockReturnValue("戻り値")
expect(mockFunc()).toBe("戻り値") // 成功する。
expect(mockFunc()).toBe("戻り値") // 成功する。

mockReturnValueOnce

Onceを付けると一度だけしか戻り値を返さない設定になります。

1
2
3
const mockFunc = jest.fn().mockReturnValueOnce("戻り値")
expect(mockFunc()).toBe("戻り値") // 成功する。
expect(mockFunc()).toBe("戻り値") // エラーになる。二度目の呼び出しの戻り値はundefinedになります。

mockResolvedValue

非同期の戻り値を取得できます。

1
2
3
4
5
it("非同期", async () => {
  const mockFunc = jest.fn().mockResolvedValue("非同期の戻り値")
  const result = await mockFunc();
  expect(result).toBe("非同期の戻り値") // 成功する。
})

存在する関数をモック化する。(spyOn)

1
const spy = jest.spyOn(Math, "abs").mockImplementation(() => 1);

spyOnを使えば上記のように存在する関数(Math.abs)もモック化することができます。

また、以下のようにspyOnで監視したclassやメソッドに対して結果をテストをすることも可能です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export class Calculator {
  sum(a: number, b: number): number {
    return a + b;
  }
}
 
it("spyon",()=>{
  const calc = new Calculator();
  const sumSpy = jest.spyOn(calc,"sum"); // spyがオリジナルの関数に憑依するイメージ
  calc.sum(2,3);
  expect(sumSpy).toHaveBeenCalledTimes(1);
  expect(sumSpy).toHaveBeenCalledWith(2,3);
 
  // 生成したspyが他のテストに影響を与えないようにspyを解除する(オリジナルの関数に戻す)ことが推奨されます。
  // もし、テストケースが多い場合はafterEachを利用して自動的に毎テストコードで解除します。
  sumSpy.mockRestore();
})

jest.fnとspyOnの使い分け

jest.fnのユースケース

  • 単純に何らかの非同期処理の戻り値として適当なコールバック関数を定義しておきたい場合

spyOnのユースケース

何かしらのモジュールの特定関数だけ置き換えたい場合はjest.fnよりspyOnで明示的に定義した方が分かりやすいです。具体的には以下のようなケースで使います。

  • ローカルストレージのメソッドremoveItemが呼ばれていることを回数チェックする。
  • APIのrequestが呼ばれていることをチェックする。

モジュール全体のモック化

jest.mock

describeやitの中ではなくトップレベルで記述する必要があります。まだパスを渡しただけの初期状態では全ての関数、メソッドは実装を持たないモックになります。

1
2
3
4
import fs from "fs";
jest.mock("fs"); // モジュールのパスを引数に指定する。この時点でfsモジュールはモック化される。
const mockFs = jest.mocked(fs);// TSだとfsがモジュール化されていると認識してくれないのでmockedでtsに対応するためにラップする。
mockFs.readFileSync.mockReturnValue("戻り値")

mockClear、mockRestore、mockReset

mockClear

mockFn.mock.calls(モックが呼ばれた回数)、mockFn.mock.instancesを初期化する。

mockRestore

jest.spyOnによって作成されたモックをオリジナルのものへ戻す。

mockReset

mockFn.mock、mockFn.mock.calls、mockFn.mock.instancesを初期化する。また、mockImplementation、mockReturnValueで設定した実装、戻り値も初期化

マッチャー

.not

検証を否定するマッチャーです。

1
expect(1+2).not.toBe(4);

toBe

string、number同士を比較する場合はこちらでも良い。仕組み的にはJavaScriptのObject.is(===)と同等の比較が行われています。なので、オブジェクト同士の比較はtoEqualじゃないとできない。

1
expect(1+2).toBe(3);

toEqual

オブジェクト同士を比較する場合はこちらを使う。

1
expect([1,2,3]).toEqual([1,2,3]);

toStrictEqual

継承元を含む厳密な比較をする。ES6以降で導入されたclass構文などを使用している場合には使えそうです。(あまりフロントエンド開発で使う機会はないかもしれません。)

toBeNull

nullになるか検証する。

toBeTruthy

trueになるか検証する。

toBeFalsy

falseになるか検証する。

toMatch

正規表現でマッチするかチェックする。

toContain

配列やオブジェクトの中に値が含まれているか検証する。

toThrow

例外をthrowするかもテスト可能です。

toHaveBeenCalled

モック関数が実行されたかテストできます。注意点としては、普通の関数だと呼び出しに失敗します。モック関数かスパイじゃないとダメです。

1
2
3
4
5
it("モック関数が呼び出されるか",()=>{
    const mockFunc = jest.fn();
    mockFunc();
    expect(mockFunc).toHaveBeenCalled();
  })

toHaveBeenCalledtimes

モック関数が呼ばれる回数をテストできます。

1
2
3
4
5
6
it("モック関数が2回呼び出される。",()=>{
  const mockFunc = jest.fn();
  mockFunc();
  mockFunc();
  expect(mockFunc).toHaveBeenCalledTimes(2);
})

toHaveBeenCalledWith

モック関数に渡される引数をチェックできます。

1
2
3
4
5
it("モック関数に渡される引数チェック",()=>{
    const mockFunc = jest.fn();
    mockFunc("あああ");
    expect(mockFunc).toHaveBeenCalledWith("あああ");
  })

テスト対象関数

1
2
3
4
export function kansu(a: number, b: number) {
  if (b === 0) throw new Error("0で割ることはできません");
  return a/ b;
}

テストコード

1
expect(()=>divide(10,0)).toThrow("0で割ることはできません")

パラメタライズドテスト

1
2
3
4
5
6
7
8
9
10
11
function sum(a: number, b: number): number {
    return a + b;
}
 
it.each`
  a    | b    | expected
  ${2} | ${3} | ${5}
  ${3} | ${-5} | ${-2}
`("$aと$bを足すと$expectedになる", ({a,b,expected})=>{
    expect(sum(a,b)).toBe(expected)
});

同じ関数内で少し引数を変えてテストケースを書きたくなるケースは多いと思います。その場合はパラメタライズドテストという記法を使用するとまとめて記述することができます。

以下のように普通に個々に指定するのと同じ記述方法になります。

スポンサーリンク
  • 2020 06.12
  • miyabisan2
  • コメントを書く
  • JavaScript
  • Tweets Twitter
  • このエントリーをはてなブックマークに追加
  • LINEで送る

関連記事

  1. 2018 07.05

    【JavaScript】「Globalオブジェクト」について

  2. 2021 01.24

    【JavaScript】「ESLint」、プラグイン、configの種類など

  3. 2020 04.12

    【JavaScript】「状態管理」や「Flux」とは?

  4. 2018 07.07

    【JavaScript】非同期処理を簡単にし、コールバック地獄から脱出させる「Promiseオブジェクト」について

  5. 2018 07.05

    【JavaScript】次世代JavaScript技術「ECMAScript2015」とは?

  6. 2018 07.16

    【JavaScript】歴史

  • コメント ( 0 )
  • トラックバック ( 0 )
  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

返信をキャンセルする。

【AWS】「RDS」の設定手順、Aurora、インスタ…

【Vue.js】テストコードを書くには?(Vue Te…

RETURN TOP

著者プロフィール

エンジニア歴10年で過去に業務系、Webデザイン、インフラ系なども経験あります。現在はWeb系でフロントエンド開発中心です。

詳細なプロフィールはこちら

スポンサーリンク

カテゴリー

  • Android
  • AngularJS
  • API
  • AWS
  • C++
  • CSS
  • cursor
  • C言語
  • DDD
  • DevOps
  • Django
  • Docker
  • Figma
  • Git
  • GitLab
  • GraphQL
  • gRPC
  • Hasura
  • Java
  • JavaScript
  • Kubernetes
  • Laravel
  • linux
  • MySQL
  • Next.js
  • nginx
  • Node.js
  • NoSQL
  • Nuxt.js
  • Oracle
  • PHP
  • Python
  • React
  • Redux
  • Rspec
  • Ruby
  • Ruby on Rails
  • Sass
  • Spring Framework
  • SQL
  • TypeScript
  • Unity
  • Vue.js
  • Webサービス開発
  • Webデザイン
  • Web技術
  • インフラ
  • オブジェクト指向
  • システム開発
  • セキュリティ
  • その他
  • データベース
  • デザインパターン
  • テスト
  • ネットワーク
  • プログラミング全般
  • マイクロサービス
  • マイクロソフト系技術
  • マルチメディア
  • リファクタリング
  • 副業
  • 未分類
  • 業務知識
  • 生成AI
  • 設計
  • 関数型言語
RETURN TOP

Copyright ©  プログラミングマガジン | プライバシーポリシー