【Elixir】moxを使用してmock用いたテストを書く

November 09, 2020

5 年前の José のこの記事、みたことがある人も多いのではないでしょうか

Mocks and explicit contracts

内容を超意訳すると「テストのために特定のモジュールに対する mock を作っちゃうと結局そのモジュールと密結合になっちゃうよね。behaviour を使って DI しよう!」っていう話です。今となってはそんなに珍しくない考え方かもしれません。

そして、上記の考えを基にして、behaviour から mock の生成を行うライブラリがmoxです。 今年の 9 月に v1.0.0 がリリースされた比較的新しいライブラリです。

dashbitco/mox - GitHub

今回はその 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 を使用したモジュール設計をはじめから進めておけばかなりスッと導入できそうですね。

このエントリーをはてなブックマークに追加