Moxを試してみる
はじめに
MoxはElixirのMockライブラリです。最近はElixirでコードを書く機会が増えているのいて、Mockをどうすれば良いのかよくわかってなかったのですが、手近なプロジェクトを覗いて見たらMoxというMockライブラリを使っていました。
Elixir界隈のデファクトみたいなのが掴みきれていないところはあるのですが。検索でもMoxが多く引っかかるので、このライブラリについて学びたいと思います。
このブログではその使い方について、まとめおこうと思います。
- Moxの基本的な使い方
Mockするな?
本筋とは少しそれるのですが、Elixir SchoolにMockについて少し気になることがかかれていました。
Elixirでのモックに対する単純な解答は、使うな、です
なるほど?と思いながらその意図について書かれた議論を読んでみるとおおよそ以下のようなことが書かれていた。
- 単語である
Mock
は動詞としてではなく、名詞として使おう - ビヘイビアでAPIの規約をきちんと決めてMockを作ろう
日本語に直すとかなり混乱しますが、要は「Mockする(動詞)のではなくビヘイビアできちんとAPIの規約を定義して、その定義を利用したMock(名詞)を作り利用せよ」と言うことみたいです。
(若干、理解がまだ甘いところがありそうなので、ご指摘等があればコメントいただけますとありがたいです)
Moxを使ってみる
前置きはさておき、Moxを使っていきます。
動作環境
このブログの内容を実施した環境は次のとおりです。
$ uname -srvmpio Linux 5.3.0-46-generic #38~18.04.1-Ubuntu SMP Tue Mar 31 04:17:56 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux $ elixir -v Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] Elixir 1.10.2 (compiled with Erlang/OTP 21)
下準備
Projectを作成し、Moxの依存を追加する
まずはMoxを使うための諸々の準備をしていきます。
以下のコマンドでMixプロジェクトを作成します。
$ mix new hello_mox
次に hello_mox/mix.exs
に依存を追加します
defp deps do [ # 依存を追加 {:mox, "~> 0.5", only: :test} ] end
プロジェクトのルートディレクトリに移動し、
mix deps.get
コマンドで依存ライブラリを取得しておきます。
(今後出てくるコマンドはすべてプロジェクトのルートで実施することを想定しています)
$ mix deps.get
これで、Moxのセットアップは終わりです。
テストするモジュールを用意しておく
実際にMoxを使う前に下準備としてテスト対象のモジュールを作成しておきましょう。
lib/service.exs
defmodule Service.GreetingBehaviour do @callback greeting() :: charlist() end defmodule Service.GreetingJapanese do @behaviour Service.GreetingBehaviour def greeting() do 'こんにちは' end end
service.ex
にはService.GreetingBehaviour
というビヘイビアとその実装を定義しています。
実装であるService.GreetingJapanese
ではこんにちわ
という文字列を返すだけの簡単な関数を用意しておきます。
次に用意した関数を呼び出すモジュールを作ります。
lin/hello_mox.ex
defmodule HelloMox do @service Application.get_env(:hello_mox, :greeting) def run() do IO.puts @service.greeting() end end
このモジュールはconfg.exs
経由でDIされたモジュールを呼び出し標準出力に出力するだけの簡単なモジュールです。
まだ、config.esx
は存在していないのでこのあとすぐ作ります。
configを用意する
まず、注意点ですが、elixirの1.9以降で、mix new
で作成するプロジェクトにデフォルトでconfig/config.exs
が作成されないようになっているみたいです。ライブラリを作成する際に、configに依存することは望ましくないとされているからです。
これは、configがグローバルな環境変数として登録されてしまうからで、ライブラリの修正等を行なう際にその自由度を下げてしまいます。
(詳細はここを参照してください)
今回に限って言えば、ただ、Moxの機能を試したいだけなので、config/config.exsを
作成しちゃいます。
$ mkdir config $ echo "import Mix.Config" >> config/config.exs
作成したconfig/config.exs
を開き以下のように編集しておきます。
import Config # 実行環境に対応したコンフィグを読み込む import_config "#{Mix.env()}.exs"
また、実行時に読み込むコンフィグであるdev.exs
も作成しておきます。
config/dev.exs
import Config config :hello_mox, greeting: Service.GreetingJapanese
これで、HelloMox.run
実行時にService.GreetingJapanese
がインジェクションされるようになりました。
実際にHelloMox.run
実行してみましょう。
$ mix run -e "HelloMox.run" Compiling 2 files (.ex) Generated hello_mox app こんにちは
想定通りに動いてそうですね。
Moxを使ってMockを作成してみる
下準備が終わったので、いよいよMoxを使っていきたいと思います。
まずはMockの定義を書いてみます。
Mockの定義は通常test_helper.exs
に書かれることが多いようです。
test/test_helper.exs
ExUnit.start() #Mockの定義を記述する。ビヘイビアに対して`Service.GreetingMock`を定義している Mox.defmock(Service.GreetingMock, for: Service.GreetingBehaviour)
次にconfigを作成します。
config/text.exs
import Config config :hello_mox, greeting: Service.GreetingMock
これで実行環境がテストの場合はHelloMox.run
実行時にService.GreetMock
がインジェクとされるようになります。
最後にテストコードを記述します
defmodule HelloMoxTest do import Mox use ExUnit.Case doctest HelloMox test "Moxでもっくをりようしてみるテスト" do Service.GreetingMock |> expect(:greeting, fn () -> 'Hello, Mox' end) HelloMox.run() end end
import Mox
をインポートし、
Service.GreetingMock |> expect(:greeting, fn () -> 'Hello, Mox' end)
の部分でMockの挙動を定義しています。
expectは引数としてMockする関数名のアトムと関数を引数として受け取ります。関数はHello, Mox
をの文字列を返します。
それではテストを実行してみます。
$ mix test Hello, Mox . Finished in 0.01 seconds 1 test, 0 failures
作成したMockのがインジェクとされ標準出力にHello, Mox
が出力されるのを確認することができました。
感想
Mox自体の使い方はさほど難しくありませんでしたが、Mixプロジェクトの周りで結構ハマりました。