JavaScript
test
testing
mocha
AsyncAwait

mochaでテストを同期的(直列)に実行する

背景

mochaでは一つのdescribeの中に直列にテストを実行する方法があります。それは、いろいろ解説があるので、ここでは触れないことにします(公式)。

つまり、すべてのテストを一つのdescribeの中に押し込んでやれば、同期的ににテストを動かすことはできるわけです。ただ、それだと本来のmochaの恩恵を受けられません。

一応、serial-mochaというライブラリはあるのですが、mochaそのものを置き換えるものなので、karmaとの接続とか、いろいろ考えるのが面倒。そこで、標準のmochaのままで、テストを同期的に実行する方法はないかと考えてみました。

結論

import AsyncLock from "async-lock";
const asyncLock = new AsyncLock();
const lock = (key) => {
  return new Promise(fulfilled => {
    asyncLock.acquire(key, done => {
      fulfilled(done);
    });
  });
};

describe("#1", () => {
  let done = null;
  let commonResource = null;
  before(async () => {
    done = await lock();
    // ここに共通の初期化処理を書く
    commonResource = await CommonResource();
  });
  it("test1", async () => {
    // テスト1(非同期可)
  });
  it("test2", async () => {
    // テスト1(非同期可)
  });
  after(async () => {
    // ここに共通の解放処理を書く
    await commonResource.close();
    done();
  });
});

解説

async-lockは、コードが同時に複数回開始されないようにロックする仕組みを提供します。この、async-lockでロックの解放を通知するためのコールバックを、Promiseとして受け取れるようにしたのが、lockという関数です。

そもそも、一つのdescribeに含まれる、複数のitは、(適切に書けば)直列に実行されるので、最初のbeforeで、ロックし、最後のafterで解放すれば、期待通りの結果が得られます。

ちなみに、itを非同期的に実行できれば、良いのですが、それをしようとすると、itが呼ばれないまま、テストが終了してしまいます。つまり、describeの中身は、itのように非同期的に実行する手段がありません。describe / itが同期的に実行されるというのは仕様上いかんともしがたいようで、それを前提にすると、上のようにinit / closeの中で、ロック/解放するという方法しか取れないことになります。

高度な利用

上のコードでは、lock()というように、lockに何も渡していませんが、lock('A'), lock('B')のようにキーを設定すると、異なるキーが設定されたテストは、非同期的に(並列に)実行されるようになります。

感想

letが気持ち悪い。けど、serial-mochaとか使うより、ずっとシンプルで良いと思う。