TypeScript(というか JavaScript) のユニットテストって何がいいのかなぁというのがわからなかったので、とりあえず自分の観測範囲でよくみる感じの Jest 試してみました。
とりあえずシンプルに以下のコマンドをうってコンソールで始めてみようと思います。
npm init -y tsc --init npm i -D jest npm i -D @types/jest npm i -D @types/node
tsconfig.json
に "outDir": "./dist",
を設定してビルド結果が dist フォルダーに出るようにします。そして package.json
の scripts
の test
に jest
を設定して、jest
の設定に rootDir
を dist
にしておきます。package.json
は以下のような感じになりました。
{ "name": "jestlab", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "jest" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@types/jest": "^24.0.15", "@types/node": "^12.6.1", "jest": "^24.8.0" }, "jest": { "rootDir": "./dist" } }
calc.ts
というファイルを作って以下のようにしてみました。
export default class Calc { public add(x: number, y: number): number { return x + y; } }
__tests__/Calc.ts
を作ってテストを書いてみる。describe
関数でテストをグルーピングして it(test関数でも同じみたい)
とか beforeEach
, afterEach
あたりでテスト本番とかテスト前処理や後処理とか書くのかな?
とりあえずこんな感じにテスト書いて
import Calc from '../calc'; describe('sum test', () => { var c = new Calc(); beforeEach(() => { c = new Calc(); }); it('1 + 1 = 2', () => { expect(c.add(1, 1)).toBe(2); }); it('2 + 2 = 4', () => { expect(c.add(2, 2)).toBe(4); }); });
npm test
叩いてみたら以下のような結果になりました。
$ npm test > jestlab@1.0.0 test /home/okazuki/labs/jestlab > jest PASS dist/__tests__/calc.js sum test ✓ 1 + 1 = 2 (7ms) ✓ 2 + 2 = 4 (1ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 1.786s Ran all test suites.
モックしたい
モック機能もあるみたいなのでついでに。
repo.ts
を作って…
export default class Repo { async get(): Promise<string> { await new Promise(r => setInterval(r, 10000)); return "Hello"; } }
これをモックしたいと思います。本番だと REST API とか呼んだりする感じ。今回も 10 秒待つのでテストのたびに呼び出されたらたまったもんじゃない感じです。
import Repo from '../repo' import RepoClient from '../repoclient'; jest.mock('../repo'); describe('mock sample', () => { it('generateMessage', async () => { var getMock = jest.fn(() => Promise.resolve('Jest hello')); (<jest.Mock>Repo).mockImplementation(() => { return { get: getMock, }; }); var c = new RepoClient(); expect(await c.generateMessage('world')).toBe('Jest hello world'); expect(getMock).toBeCalled(); }); });
とりあえずモックしたいモジュールを import
して jest.mock
でモックに差し替えるように設定する。
その後 jest.Mock
にキャストして mockImplementation
で new された時のダミー実装を返す感じ。単純な関数のモックは jest.fn
でやるみたい。jest.fn
に対しては toBeCalled
みたいな専用のアサート関数があるようです。
なるほどね。import をモックを差し込むポイントとして使う機能があるのなら DI コンテナみたいな仕組みなくてもテスト出来ますね。
モック実装もタイプセーフな雰囲気にしたかったらインターフェース??repo.ts
を、とりあえずこんな感じにして
export interface Repo { get(): Promise<string>; } export class RepoImpl implements Repo { async get(): Promise<string> { await new Promise(r => setInterval(r, 10000)); return "Hello"; } }
repoclient.ts
は RepoImpl
を使って
import {RepoImpl} from './repo'; export default class RepoClient { private readonly repo = new RepoImpl(); public async generateMessage(x: string) { var r = await this.repo.get(); return `${r} ${x}`; } }
テストはインターフェースのモックを返すみたいな?
import {RepoImpl, Repo} from '../repo' import RepoClient from '../repoclient'; jest.mock('../repo'); describe('mock sample', () => { it('generateMessage', async () => { var getMock = jest.fn(() => Promise.resolve('Jest hello')); (<jest.Mock>RepoImpl).mockImplementation(() => { return { get: getMock, } as Repo; }); var c = new RepoClient(); expect(await c.generateMessage('world')).toBe('Jest hello world'); expect(getMock).toBeCalled(); }); });
こうしておくとモックの実装部分で型の間違いが検出出来て便利かも?試しに間違えてみた。
まとめ
とりあえず動いたけど、世間とあってるのだろうか。