Edited at
ElixirDay 15

elixirでmeckをつかう

More than 1 year has passed since last update.

Elixir Advent Calendar 2013 15日目です。

pure elixirネタではないのですが、erlangとの相互運用性もelixirの良さということで、ご了承を。

meck はmockをerlangで実現するモジュールですがelixirでも使えます


使いかた

自分のプロジェクトのmix.exsに


mix.exs

        defp deps do

[ { :meck, "~> 0.1", github: "exproxus/meck" } ]
end

を入れておきます。これでmix deps.compileすることで、自分のプロジェクトでmeckが

使えるようになります。


モックの生成

テストコード中で

        :meck.new(Module, [:passthrough])

これでModuleがモックになります。:passthroughというのは、モックとして仕込ん

でいない関数が呼ばれたときには本物を呼び出すという指定です。passthrough以外

にもいろいろ指定できるが詳しくはmeckのドキュメントを参照のこと。このままだと

ただ本物にフォワードするだけのモックなので、芸を仕込みます。

作られたモックに芸を仕込むのはいくつかの方法があります。

方法1

    :meck.expect(Module, :func, fn(args) -> returnvalue end)

関数funcに対して無名函數を対応付ける方法でです。自由度が高いが煩雑に

慣るきらいがあるので通常はもっとベタな方法をつかいます。

方法2

    :meck.expect(Module, :func, [{args_spec1, ret_spec1},

{args_spec2, ret_spec2},...])

関数funcに対して引数スペックと戻り値スペックペアのリストを指定する、極

めてベタな方法です。書式だけ見ると分かりにくいかもしれないけど、実際に

書いてみると、あとで示しますが、極めてベタ。

引数スペックは、値を書けばよいが、簡単なパターンマッチも使えます。

任意の値を示す"_"はアトムとしての:"_"を用います。

戻り値スペックは、戻値の他に例外の発生などで以下の形式となります。

    ret_spec -> :meck.val(exp)   expを返す|

:meck.seq(list) listの要素を順に返す|
:meck.loop(list) listの要素を繰り返して返す|
:meck.raise(:throw, reason) 例外を発生|
:meck.raise(:error, reason) エラー例外を発生|
:meck.raise(:exit, reason) exit例外を発生

例:

TargetModuleモジュールを開発することがお仕事だとします。

TargetModuleは単純なExternalModuleへのプロキシだとして、こんなかんじに

実装したとします。


target_module.exs

    defmodule TargetModule do

def priv(a) do
ExternalModule.priv(a)
end
end

しかし必要なExternalModuleモジュールはまだまだ開発されていなくて、

仕様だけが決っているとします。

ExternalModule.priv/1の仕様として

    ExternalModule.priv("a") -> "a"

ExternalModule.priv("b") -> "b"
ExternalModule.priv({_, _}) -> "badmatch"
ExternalModule.priv(_) -> "c"

だとします。

このような場合にTargetModule開発者のexunitはどうなるでしょうか。

exunitにはsetupとteardownがありますのでそこで初期化をします。


target_module_test.exs

        setup do

:meck.new(ExternalModule, [:passthrough])
:meck.expect(ExternalModule, :priv,
[
{["a"], :meck.val("a")},
{["b"], :meck.val("b")},
{[{:"_", :"_"}], :meck.raise(:throw, "badmatch")},
{[:"_"], :meck.val("c")},
])

end
teardown do
:meck.unload(ExternalModule)
end


このように、極めてベタな感じにモックの振る舞いを記述できます。ほとんど仕様そのまま

書けるのが良いですね。もちろんコレくらいなら自前でモックを作っても同じなのですが。。。。

これでexunitを動かすとExternalModuleをモックにしたてて、終了するとアンロード

するようになります。あとは、ガシガシテストを畫いていきます。


elixir

      test "priv-mock" do

assert("a" == TargetModule.priv("a"))
assert("b" == TargetModule.priv("b"))
assert("c" == TargetModule.priv("c"))
assert("c" == TargetModule.priv("d"))
assert catch_throw(TargetModule.priv({"d", "a"})) == "badmatch"
end

こんなかんじです。

つぎは、@alucky0707 さんです!