LoginSignup
3
1

More than 5 years have passed since last update.

Mockery を試してみる

Posted at

vsts-task-libで使われている mockeryの挙動が謎だったので、ハローワールドレベルから試して見た。とっても、しょーもないことでクソハマったが、それも含めて書いておく。

mockery の mock 方式

ドキュメントを読んでいると、node のモックは難しいらしく、普通のフレームワークとは違う方式をとっている様子。基本的な話をすると、require(...) が呼ばれたタイミングで、require の先のモジュールをモックにごっそり入れ替えるという方式みたい。

ちなみに、そういう方式なので、

test.js -> index.js -> special.js

といった感じになっているときに、require('index.js') にも、require('special.js') にもモックをかけられる。つまり、モジュールを読んだ先のモジュールもモックができる。

コードサンプル

コードを書いてみよう。vsts-task-lib もそうなので、あえて、typescript で記述する。

test.ts

var expect = require('chai').expect;
var Index = "../index";
var mockery = require('mockery');

var mock_index_module = {
    request: function() {
        return "I'm mocking";
    }
}
var mock_special_module = {
    get_rest_api: function() {
        return 'mock special rest api';
    }
}

describe('Array', function() {

    describe('mockery testing', function () {
        it('is no mock testing', function() {
            var index = require(Index);
            expect(index.request()).to.equal('real http request result');            
        });
        it('mocks index', function() {
            mockery.registerAllowable(Index);
            mockery.registerMock(Index, mock_index_module);
            mockery.enable({ useCleanCache: true});
            var index = require(Index);
            expect(index.request()).to.equal("I'm mocking");
            // expect(index.other_request()).to.equal('real other response body'); // error
            mockery.disable();
            mockery.deregisterAll();
        })

        it('mocks special', function() {
            mockery.registerAllowable(Index);
            mockery.registerMock("./special", mock_special_module);
            mockery.enable({ useCleanCache: true}); // 要注意。指定しないと動かない。useCleanChache と間違えてた。
            var index = require(Index);
            expect(index.request()).to.equal("mock special rest api");
            mockery.disable();
            mockery.deregisterAll();
        })
    });
});

index.ts

import special = require('./special');

function request() {
    return special.get_rest_api();
}

function other_request() {
    return special.get_other_api();
}

exports.request = request;
exports.other_request = other_request;

special.ts

export function get_rest_api() {
    return 'real http request result';
};


export function get_other_api() {
    return 'real other response body';
}

ポイントはいくつかあって、mock をかけたいときは、mock を登録して、有効化する必要がある。
使っているメソッドを解説して行きたい。サンプルでは、一段めのモジュール、二段目のモジュールに両方モックをかけている。

mockery.registerAllowable('./some_mock_package')

registerAllowable は、モック対象のパッケージとその配下のパッケージに関してモックをかけるぞと宣言するもの。実際は、その配下のrequire でモックをしなかったらワーニングが出てしまうのでそれを防ぐために記述する。同時に書こうと思ったら

mockery.registerAllowables(['async', 'path', 'util']) 

という感じでもかける。終わったら deregisterAllowable をしておく。

mockery.registerAllowable('./my-source-under-test', true)

という風にもかけるらしい。node はデフォルトでは、モジュールを1回しか読まない。同じパッケージに対して違うMockをかけたい時もあるだろう。そういう時は、unhooking というらしいが、上記のように、true を指定することで、nodeのモジュールが一旦ディレジスターされる。

mockery.registerMock("./special", mock_special_module)

これは、Mock するモジュールを指定する。./special の部分は、ワイルドカードとか、かしこい機能がないので、require で呼ばれる時と全く同じ記述が必要らしい。mock_special_module はモック対象を記述する。こんな感じ。

var mock_index_module = {
    request: function() {
        return "I'm mocking";
    }
}

mockery.enable({ useCleanCache: true})

Mock を有効化している。ここの部分で気をつけるのがキャッシュだ。ここでは、useCleanCachetrue をセットしている。私もキャッシュの恐ろしさを思い知った(クソハマった)node モジュールのexport は常にキャッシュされる。これは、モックをものすごく難しくしている。mockery は、モジュールのキャッシュをクリアするメカニズムを持っている。このオプションを指定すると、以前のキャッシュうは消されるので良い。

私がハマったのは、useCleanCache をスペルミスしていて、気づかず、エラーも特にでずなぜか Mockがかからないという状態になった。他にも、mockery.resetCache() なんてメソッドもある様子。

var index = require('./some_mock_package')

あとは、Mock 対象のモジュールを読み込めばいい。実際にモックがかかるのが、そのモジュールの配下としても、一段目のモジュールを指定する。ここのモジュールはregisterAllowable と同じになるはず。mockeryを有効化して、requireをした瞬間から、この配下のrequire に登録したmockがあれば、差し替えられる。

ちなみに、Mockのモジュールは、差し替え対象のモジュールが、function A, function B を持っていたとすると、両方の記述が必要になる。(実際は、function A だけモックしたいケースもあるだろうが、そのケースは、function B` から本物にデリゲートするのが良さげかも。

終わりに

Mockery の動作イメージは掴めたと思う。ただ、issue を見ていると、多くの人が、mockがかからない問題をレポートしている。ただ、私のように、issueを見ると、初期化のタイミングのミスだったり、そういう、普通のMockフレームワークと使い勝手が違うところに起因するようだ。キャッシュが難しいとよくよくわかったのであった。

ちなみに、サンプルソースは、上げておいた。

本当はここからもっと実験して見たいところだが、先ほどのしょうもない問題で引っかかって今2時なので寝ることにする。無念。しょーもないものほどハマってしまう。キャッシュは恐ろしいということで。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1