ジンジャー研究室

長めのつぶやき。難しいことは書きません。

シングルトンモジュールのテスト

シングルトン(singleton.js)。

module.exports = {
    value: 0
};

その値をインクリメントするやつ(incr.js)。

var singleton = require('./singleton.js');

module.exports = function incr() {
    singleton.value++;
};

incr()を呼んだら値が増えますというテストを2つ記述。

var assert = require('assert');
var incr = require('../src/incr.js');

describe('a', function() {
    it('1', function() {
        var singleton = require('../src/singleton.js');
        incr();
        assert(singleton.value === 1);
    });
    it('2', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');
        incr();
        assert(singleton.value === 1);
    });
});

なんと同じテストなのに2つ目だけ失敗!ガーン!

改良?

シングルトンだからそりゃそうだよねと思って、調べてみたらrequireにキャッシュされているモジュールを削除できるらしい。というわけで、使い終わったシングルトンを削除っと。

var assert = require('assert');
var incr = require('../src/incr.js');

describe('a', function() {
    it('1', function() {
        var singleton = require('../src/singleton.js');
        incr();
        assert(singleton.value === 1);
        delete require.cache[require.resolve('../src/singleton.js')];//追加
    });
    it('2', function() {
        var singleton = require('../src/singleton.js');
        incr();
        assert(singleton.value === 1);
    });
});

また失敗!ズコー!

ちなみにこの時のsingleton.value0。実はincrが参照しているのは最初にrequireしたシングルトンなので、消したつもりで消えていなかった。

これも駄目

内部でrequireしてみたけど、incr自体がキャッシュされているのでこれも駄目。

var assert = require('assert');

describe('a', function() {
    it('1', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');//移動
        incr();
        assert(singleton.value === 1);
        delete require.cache[require.resolve('../src/singleton.js')];
    });
    it('2', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');//移動
        incr();
        assert(singleton.value === 1);
    });
});

直った

最終的にこうすると直る。

var assert = require('assert');

describe('a', function() {
    it('1', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');
        incr();
        assert(singleton.value === 1);
        delete require.cache[require.resolve('../src/singleton.js')];
        delete require.cache[require.resolve('../src/incr.js')];//追加
    });
    it('2', function() {
        var singleton = require('../src/singleton.js');
        var incr = require('../src/incr.js');
        incr();
        assert(singleton.value === 1);
    });
});

より一般的には、require.cacheのkeyで回して全部消す。中には初期化がクソ重たいモジュールがあるかもしれないが、そんなことを気にしてはいけない。

というわけで、この例くらい単純ならいいんだけど、思わぬところに思わぬ依存があって死ぬので確実に全モジュールを削除 & 毎回関数内でrequireを徹底する必要があるという話。最近Fluxなアプリがシングルトンモジュールをせっせと作っていて、こういうテストをするためにJestに依存するの嫌だなーと思っている。

あと、効果音がちょっと古い。