テストフレームワーク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); // ここで例外が発生するため、Rejectedなpromiseオブジェクトが返される 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); }); }) });