JestでTypeScriptのtest
Jest
Jestをnpm install
npm install jest --save-dev
packaga.jsonに以下を追加。npm test
でtestが実施されるようになる
"scripts": {
"test": "jest"
},
この時点でnpm test
をすると以下のように表示される。
No tests found, exiting with code 1
Jestはデフォルトで*.spec.js
という名前のファイルを探してテストを実行する。
ts-jest
JestはJavaScipt用のテストフレームワークであり、TypeScript用にはts-jestがある。ts-jestはTypeScriptからJestへのブリッジができ、ts-jestはJavaScriptを生成せずにTyepeScriptのtestを実行できる。
ts-jestのインストールと、configureは以下のコマンドで実施できる。
npm install ts-jest --save-dev
npx ts-jest config:init
npm install typescript --save-dev
npm install @types/jest --save-dev
試しに最初のTypeScriptのテストを書いてみる。
test('should be false!!!', () => {
expect(true).toBeFalsy();
});
test関数は二つの引数を受け取り、第一引数はテスト名を受け取り、第二引数で実際の実行する関数を受け取る。ここでは「should be false!!!」というテスト名、Jestのexpect関数を受け取っている。
これを実行すると下記のような表示になる。
watch mode
プロジェクト内のファイルが変更された際、自動でテストを走らせることもできる。package.jsonに下記のように記述。
{
...
"scripts": {
"test": "jest --watchAll --verbose"
},
...
}
デフォルトでは全てのテストがファイルの変更があった際実施される。
いくつかのショートカットが用意されており、例えばf
キーでfailedテストのみ実行するといったことも可能。o
キーで関連テストのみ実行といったことも可能。
group test
ロジック毎にテストをグルーピングしたい場合は、下記のようにできる。
describe("a group of tests", () => {
test("first test", () => {
expect("string value").toEqual("string value")
})
it("second test", () => {
expect("hello").not.toEqual("world");
})
it("third test", () => {
expect("abc").not.toEqual("def");
})
});
Matchers
いくつかのMatcherを紹介。他のものは公式ドキュメント参照
.toBe(value)
プリミティブな値が等しいかのtest。
これはfalse。
it("toBe", () => {
expect(1).toBe(2);
});
.toEqual(value)
オブジェクトインスタンスの値が等しいかのtest。
trueになる。
it("should match with toEqual", () => {
let objA = { id: 1 };
let objB = { id: 1 };
expect(objA).toEqual(objB);
});
.toContain(value)
アイテムが含まれているかのtest.これはtrue.
it("should contain a value", () => {
expect("abcde").toContain("cde");
});
いくつかのMathcerはnot
を使用できる。
it("should not contain a value", () => {
expect("abcde").not.toContain("123");
});
.toContainEqual(value)
特定のアイテムが配列内に含まれるかのtest。trueになる
it("should contain an array item", () => {
let objArray = [
{ id: 1 },
{ id: 2 }
];
expect(objArray).toContainEqual({ id: 2 });
});
.toThrowError(error?)
エラーが呼ばれるかのtest。↓はtrue.
function throwsError() {
throw new Error("this is an error");
}
it("should throw an error", () => {
expect(
() => { throwsError() }
).toThrowError(new Error("this is an error"));
});
Setup and TearDown
テストを実行する前、もしくは後に、特定のclassをインスタンス化したり、値を用意したい場合がある。
例えば以下のようなclassがあり、それぞれのテスト実行時にこのクラスをインスタンス化したい。
class GlobalCounter {
counter: number = 0;
increment() {
this.counter++;
}
}
下のコードでは、beforeAll
でGlobalCounterのインスタンスを作成しglogalCounter変数に格納している。これはdescribe内の各テストの前に毎回実施される。
そしてbeforeEach
でcounterプロパティに0を格納。
最後にafterEach
で各テストの実行後にconsole.logで結果を出力している。
describe("a group of set up tests", () => {
let globalCounter: GlobalCounter;
beforeAll(() => {
globalCounter = new GlobalCounter();
})
beforeEach(() => {
globalCounter.counter = 0;
})
afterEach(() => {
console.log(`global Counter count: ${globalCounter.counter}`);
})
it("first test", () => {
globalCounter.increment();
expect(globalCounter.counter).toEqual(1);
})
it("second test", () => {
expect(globalCounter.counter).toEqual(0);
})
it("third test", () => {
globalCounter.increment();
globalCounter.increment();
expect(globalCounter.counter).toEqual(2);
})
})
mock
mockのテストもできる。
class MyCallBack {
public execCallBack(
value: number,
callback: (value: number) => number
) {
console.log("executing CallBack");
callback(value);
}
}
describe("mock test", () => {
let mockCallBack = jest.fn(x => x + 1);
let myCallBack = new MyCallBack();
it("should call the callback", () => {
myCallBack.execCallBack(1, mockCallBack);
expect(mockCallBack).toHaveBeenCalled();
})
it("returned value is plus 1 on parameter", () => {
myCallBack.execCallBack(1, mockCallBack);
expect(mockCallBack.mock.results[0].value).toBe(2);
})
});
例えば、引数に1を足して返すというcallBackを使用するclassがある。
このcallBackを上記のようにjest.fn
でmock化し、 MyCallBack()クラスのMyCallBackexecCallBack
の関数に渡している。
一つ目のテストではcallBackが呼ばれたことをテストし、二つ目のテストでは、引数に1を足した数字が返ってくることをテストしている。上のコードではmockCallBack.mock.results[0].value
の部分でcallBackの返却値を受け取れる。
async await
async await構文もテストで使用できる。
class AsyncAwait {
delayedPromise(): Promise<string> {
return new Promise<string>(
(resolve: (str: string) => void,
reject: (str: string) => void) => {
setTimeout(() => {
console.log(`2. returning success`);
resolve('success');
}, 1000)
}
)
}
}
例えば、1秒後に"success"という文字列を返すPromiseを扱うクラスがある。
これをasync awaitを使用して下記のようにテストできる。
describe('async test with await', () => {
let asyncWithPromise = new AsyncAwait();
it('should return success', async () => {
console.log(`1. calling delayedPromise`);
let result = await asyncWithPromise.delayedPromise();
console.log(`3. expecting result`);
expect(result).toBe('success');
})
});
console.logも1→2→3の順で実行されているのが確認できる。
jsdom
jsdomというライブラリを使ってHTMLのテストもできる。
必要なライブラリをインストール
npm install jsdom --save-dev
npm install jquery
npm install @types/jsdom --save-dev
npm install @types/jquery --save-dev
npm install --save-dev jest-environment-jsdom
jest.configに下記を追記。
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jest-environment-jsdom',
};
テストコード。
ここではsetTestDivという関数を用意して、ここでは引数で渡ってきた文字列で<div id="test_div"></div>
のHTMLを更新できることをテストしている。
/**
* @jest-environment jsdom
*/
import * as $ from 'jquery';
const setTestDiv = (html: string) => {
$("#test_div").html(html);
}
it('should set text on div', () => {
document.body.innerHTML = '<div id="test_div"></div>';
let html = $("#test_div");
setTestDiv('hello world');
expect(html.text()).toBe('hello world');
});
Discussion