5 年前の José のこの記事、みたことがある人も多いのではないでしょうか
内容を超意訳すると「テストのために特定のモジュールに対する mock を作っちゃうと結局そのモジュールと密結合になっちゃうよね。behaviour を使って DI しよう!」っていう話です。今となってはそんなに珍しくない考え方かもしれません。
そして、上記の考えを基にして、behaviour から mock の生成を行うライブラリがmoxです。 今年の 9 月に v1.0.0 がリリースされた比較的新しいライブラリです。
今回はその mox を使用して、mock を使ったテストを使用するサンプルを紹介します
Behaviour を使用したモジュールの作成
mox は前述のようにこの記事の思想を基に作成されたライブラリです。
そのため Behaviour を使用したモジュールを作成する必要があります。 (個人的にはこのライブラリを使用する云々関係なく、Behaviour を適宜使用するべきだと思いますが、そういった話はいつか別の記事にします)
Behaviour を使用したモジュールの作成に関しては以下の別記事を参照してください。 今回のサンプルでは以下の記事で紹介しているモジュールをそのまま使用しています
Elixir で Behaviour を使用した実装を行うのに必要な基礎知識
mox の依存の追加
defp deps do
[
#..(略)
{:mox, "~> 1.0", only: :test} #add
]
end
moxk の生成の設定
Mox.defmock
を使用して mock を生成します
Mox.defmock(ExTwitterMock, for: ExTwitter.Behaviour)
Mox.defmock(TsundokuBuster.Repository.UserMock, for: TsundokuBuster.Repository.UserBehaviour)
Behaviour を指定することでそれを基にした mock が生成されます
当たり前ですが、ExTwitterなど外部のライブラリの Behaviour のモックを使用することも可能です
config で DI
test 環境で使用する module を mock に指定します
config :tsundoku_buster,
twitter_client: ExTwitterMock,
user_repo: TsundokuBuster.Repository.UserMock
test 書いてみる
defmodule TsundokuBuster.Usecase.UserTest do
alias TsundokuBuster.Usecase.User, as: UserUsecase
alias TsundokuBuster.Schema.User
alias TsundokuBuster.Repository.UserMock
use ExUnit.Case, async: true
import Mox
setup :verify_on_exit!
describe "get_authorize_url/0" do
test "APIへのリクエストが全て成功した場合authorize_urlが返される" do
ExTwitterMock
|> expect(:request_token, fn ->
{:ok, %ExTwitter.Model.RequestToken{oauth_token: "token"}}
end)
|> expect(:authorize_url, fn "token" -> {:ok, "url"} end)
assert UserUsecase.get_authorize_url() == {:ok, "url"}
end
test "APIへのリクエストに何かしらのエラーが起きた際にはエラーがかえる" do
ExTwitterMock
|> expect(:request_token, fn -> {:error, :reason} end)
assert UserUsecase.get_authorize_url() == {:error, :reason}
end
end
end
かなりシンプルに記述できますね。expect で呼び出しが期待される関数を列挙していき、それに基づいたテストが行われます。 expect しているのに呼び出されていない関数があれば以下のようなエラーでテストが失敗します
* expected ExTwitterMock.access_token/2 to be invoked once but it was invoked 0 times
終わりに
Behaviour を使用したモジュール設計をはじめから進めておけばかなりスッと導入できそうですね。