テストフレームワークmochaでの、非同期のテストの書き方について。
mochaの導入方法などはこちらを参照。
1年近く前のエントリだが、そんなに間違ったことは書いていないはず。
なぜ非同期のテストには工夫が必要なのか
mochaでは、done
を使うことで、簡単に非同期のテストを書ける。
しかし、done
について説明する前に、そもそもなぜdone
を使う必要があるのかを説明する。
これは、非同期のテストに工夫が必要である理由の説明でもある。
テストのなかで非同期の関数を使うと、非同期の処理が行われる前にテストが終わってしまう。
非同期の処理を待つことなくテストが終了してしまうため、テストが機能しない。
これが、非同期のテストには工夫が必要な理由である。
fs.readFile()
を例に、具体的に説明する。
この関数は指定したファイルを読み込むものだが、非同期で実行される。
const fs = require('fs');
fs.readFile('./hoge.txt', 'utf8', (err, data) => {
if(err) throw err;
console.log(data);
})
console.log('finish!');
読み込ませるファイルを用意した上で上記のコードを実行すると、finish!
と表示された後に、読み込んだファイルの内容が表示される。
つまり、fs.readFile()
の結果を待つことなく、プログラムは進んでいく。
テストでも同じように動くため、問題が発生する。
const assert = require('assert');
const fs = require('fs');
describe('async test', () => {
it('readFile', () => {
fs.readFile('./hoge.txt', 'utf8', (err, data) => {
assert(false);
})
})
});
assert(false)
としているので、本来はここでエラーとなり、テストはパスしない。
だが実際には、このテストコードを実行するとパスしてしまう。
fs.readFile()
のコールバックの実行を待つことなくプログラムが進んでいき、assert(false)
が呼び出される前にテストが終わってしまうからだ。
これを解決し、テストできるようにするのが、done
である。
const assert = require('assert');
const fs = require('fs');
describe('async test', () => {
it('readFile', (done) => {
fs.readFile('./hoge.txt', 'utf8', (err, data) => {
assert(false);
done();
})
})
});
使い方は簡単で、it
の仮引数にdone
を渡せばいい。
そうすれば、そのテストコードでdone()
が呼ばれるまではテストは終了せず、コールバックが実行され、assert(false)
も実行される。
doneを使ったPromiseのテスト
Promiseのテストも、done
によって書くことが出来る。
下記は、引数を足し合わせた結果を非同期で返す関数asyncAdd()
のテスト。
先程と同様にdone()
が呼ばれるまで待つことで、非同期の結果についてテスト出来る。
const assert = require('assert');
function asyncAdd(a, b) {
return new Promise((resolve) => {
resolve(a+b);
});
};
describe('async test', () => {
it('Promise', (done) => {
let result = 0;
asyncAdd(2, 3).then((result) => {
assert(result === 5);
done();
});
})
});
しかし、このコードには一つ問題がある。テストが通らなかった際にdone()
が呼ばれず、テストが正常に終了しない。
テストを次のように書き換えてテストが通らないようにした場合、Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
というエラーが発生する。
describe('async test', () => {
it('Promise', (done) => {
let result = 0;
asyncAdd(2, 3).then((result) => {
assert(result === 9);
done();
});
})
});
これを回避するためには、以下のように書く。
describe('async test', () => {
it('Promise', (done) => {
let result = 0;
asyncAdd(2, 3).then((result) => {
assert(result === 9);
}).then(done, done);
})
});
テストがパスすれば最後のthen()
の第一引数のdone()
が、失敗した場合は第二引数のdone()
が呼ばれるため、done()
が呼ばれないという状況を回避できる。
doneを使わないPromiseのテスト
mochaでは、done()
を使わずにPromiseのテストを書くことも出来る。
テストするPromiseオブジェクトをreturn
するようにして、そのthen()
のなかにアサートを書く、という方法である。
以前書いたJestでの方法と同じだ。
JestでReactのテストをする(6) 非同期のテスト
const assert = require('assert');
function asyncAdd(a, b) {
return new Promise((resolve) => {
resolve(a+b);
});
};
describe('async test', () => {
it('return Promise', () => {
let result = 0;
return asyncAdd(2, 3).then((result) => {
assert(result === 9);
});
})
});
参考資料