AVAのtest('コメント', () => {/* テスト内容 */});
という書き方(xUnit形式)はJestでも可能です。AVAではimport test from 'ava';
という感じでtest
関数をインポートしていましたが、Jestではグローバル変数として定義されているのでインポートは不要です。test
関数だけでなく、Jestが提供するAPIは全てグローバル変数で定義されているので、import
せずに参照できます。
// AVA
import test from 'ava';
test('テストケース名など', t => {
t.true(true);
});
// Jest
// 全てのAPIはグローバルに定義されており、importは不要
test('テストケース名など', () => {
expect(true).toBe(true);
});
JestではRSpecやmochaのようなdescribe
, it
を使ったSpec形式のテスト記述が可能です。また、describe
の中でtest
関数を使ってテストケースを定義することも可能です。
// AVA
// AVAにはxUnit形式しかない
// Jest
describe('コメント', () => {
describe('コメント', () => {
it('コメント', () => {/* テスト内容 */});
// test関数も利用できる
test('コメント', () => {/* テスト内容 */});
});
});
テストの事前処理、事後処理については、名前がちょっと違うだけの同じ挙動のAPIが用意されています。
// AVA
import test from 'ava';
// ファイル単位の事前処理、事後処理
test.before(t => { ... });
test.after(t => { ... });
// テストケース単位の事前処理、事後処理
test.beforeEach(t => { ... });
test.afterEach(t => { ... });
// Jest
// ファイル単位の事前処理、事後処理
beforeAll(() => { ... });
afterAll(() => { ... });
// テストケース単位の事前処理、事後処理
beforeEach(() => { ... });
afterEach(() => { ... });
describe
内に事前処理、事後処理を書くと、そのdescribe
内で有効な事前処理・事後処理を記述できます。
// AVA
// AVAではできない。
// 事前処理・事後処理を分けたければ、ファイルを分けるしかない
// Jest
// ファイル内で一度だけ実行される
beforeAll(() => { ... });
afterAll(() => { ... });
// ファイル内のテストケース単位の事前処理・事後処理
beforeEach(() => { ... });
afterEach(() => { ... });
describe('コメント', () => {
// このdescribe内で一度だけ実行される
beforeAll(() => { ... });
afterAll(() => { ... });
// このdescribe内のテストケース単位の事前処理・事後処理
beforeEach(() => { ... });
afterEach(() => { ... });
});
参考ドキュメント
AVAでは、t.context
変数を介して事前処理・事後処理・テストケース間でデータを共有することができました。
// AVA
import test from 'ava';
test.before(t => {
t.context.hoge = 'hoge';
});
test('...', t => {
t.is(t.context.hoge, 'hoge'); // Pass
});
Jestには、AVAのt.context
のようなAPIはありませんので、let
で変数を定義し共有します。
// Jest
let hoge;
beforeAll(() => {
hoge = 'hoge';
});
test('...', () => {
expect(hoge).toBe('hoge');
});
AVAとJestの非同期処理のテストの違いについて書きますが、ほぼ変わらないです。
Promiseの結果を待ちたい場合、test
やit
に渡す関数をasyncにすれば、awaitしてくれます。
// AVA
import test from 'ava';
test('…', async t => {
const result = await getData(…);
…
});
// Jest
test('…', async () => {
const result = await getData(…);
…
});
it('…', async () => {
const result = await getData(…);
…
});
事前処理・事後処理も同様にasync関数にしPromiseを返す処理にawaitをつければ、Promiseの完了まで待ってくれます。
// Jest
beforeEach(async () => {
await someAsyncFunc();
});
また、Jestではexpect(…).resolves
、または、expect(…).rejects
を使うことで、アサーション単位でPromiseの解決を待ってくれます。
// Jest
test('…', () => {
// getDataAsyncが返すPromiseがresolveするのを待って比較
expect(getDataAsync()).resolves.toEqual(…);
});
コールバックの実行を待ちたい場合、AVAではtest.cb
を利用すると、t.end
が呼び出されるまでテストケースの終了を待ちます。
// AVA
import test from 'ava';
test.cb('…', t => {
someAsyncFunction(() => {
…
t.end(); // テストケースが終了
});
});
Jestでは、test
やit
に渡す関数の引数を受け取るように記述すると、その引数が実行されるまでテストケースの終了を待ちます。
// Jest
test('…', done => {
someAsyncFunction(() => {
…
done(); // テストケースが終了
});
});
Assertion Planningとは、テストケース内で何度アサーションが呼ばれるかを事前に設定し、設定した回数と実際にアサーションが呼ばれた回数に違いがあった場合にテストケースを失敗させる機能です。
なぜAssertion Planningが必要なのかについては、以下のブログが詳しいです。ざっくり言うと、JSは非同期処理が多いため、テストケースが成功したと思っても実は想定していた実行パスを通っておらずアサーションが実行されなかっただけということが起こりえます。Assersion Planningによってアサーションの実行回数を定義することで、そのような「想定していた実行パスを通過せず、アサーションが実行されなかった」という状態を検知することが可能になります。
おまえは今まで実行したassertの回数を覚えているのか?あるいは新しいアサーションユーティリティのご提案 - teppeis blog
AVAでは、t.plan
を使ってアサーションの回数を宣言します。
// AVA
import test from 'ava';
test('…', t => {
t.plan(1);
try {
…
} catch(err) {
// エラーがthrowされない場合でもt.planによってテストが落ちる
t.is(err.message, '…');
}
});
Jestでは、expect.assertions
を使います。
// Jest
test('…', () => {
expect.assertions(1);
try {
…
} catch(err) {
// エラーがthrowされない場合でも
// expect.assertionsによってテストが落ちる
expect(err.message).toBe('…');
}
});
test.each
を使うと、テーブル駆動テスト(Table Driven Testing)が可能です。
test.each([
// [actual, expected]
[1, 1],
[1, 2]
])('case %#', (actual, expected) => {
expect(actual).toBe(expected);
});
describe.each
もあります。
describe.each([
// [actual, expected]
[1, 1],
[1, 2]
])((actual, expected) => {
it('…', () => { … });
it('…', () => { … });
it('…', () => { … });
…
});
参考ドキュメント
test.each
describe.each
ドキュメント
- AVA → Jest
t.truthy(value)
→expect(value).toBeTruthy()
t.falsy(value)
→expect(value).toBeFalsy()
t.true(value)
→expect(value).toBe(true)
t.false(value)
→expect(value).toBe(false)
t.is(value, expected)
→expect(value).toBe(expected)
t.not(value, expected)
→expect(value).not.toBe(expected)
t.deepEqual(value, expected)
→expect(value).toEqual(expected)
t.notDeepEqual(value, expected)
→expect(value).not.toEqual(expected)
t.throws(fn)
→expect(fn).toThrows()
t.notThrows(fn)
→expect(fn).not.toThrows()
t.regex(contents, regex)
→expect(contents).toMatch(regex)
t.notRegex(contents, regex)
→expect(contents).not.toMatch(regex)