4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Moxの使い方

Last updated at Posted at 2023-09-18

目次

天気予報API

以下をdepsに追加

{:mox, "~> 1.0", only: :test},
{:req, "~> 0.1"},

libとtestディレクトリはこのような構成にする予定です。

lib
├ weather.ex
└ weather
  ├ behaviour.ex
  └ impl.ex
test
├ test_helper.ex
└ weather_test.ex

Mock化とは

外部APIにアクセスする関数のテストを記述する際、通常の実行と同じく外部APIにアクセスする必要があります。

そうなるとテストの実行時間が伸びてしまい、全体のテストの終了時間も遅くなってしまう問題が発生します。

そういった関数はMock化することによって外部APIを利用せずに済み、テスト実行時間を省略することが可能になります。

Mock化とは関数の処理を模倣することで、Behaviour(振る舞い、出力がどのようなものであるか)を定義し、expect(期待通りの振る舞い)を設定することです。

ElixirでMockを定義するライブラリがMoxである。

使用するメリット

まず外部APIには呼び出す状況(日付など)で出力結果が変わるものがあります。
例として天気予報や株価のデータを取得するAPIなどが挙げられます。

Mockを使用すると安定した出力結果が得られ、出力結果を使用した画面描画などの再現が可能になります。

また外部APIがエラーを出力したときも再現できるので、耐障害性を上げることができます。

基本的な使い方

次の3ステップで関数のMock化を行う

  1. Behaviourを定義する
  2. Behaviourを実装した関数を作成する。
  3. 実装をカプセル化する
  4. MockをBehaviourに基づいて設定する。
  5. テストを作成し、Mockのexpectを設定する

Mock化を行うにあたって次のファイルを作成します。

ファイル 説明
/lib/weather/behaviour.ex Behaviorを作成
/lib/weather/impl.ex Behaviorを実装
/lib/weather.ex 実装をカプセル化、ここから呼び出す。
/test/weather.ex Mockのexpectを設定してテストを行う。

Behaviour(動作)を定義する

Mockを作成するには、関数とMockが同じBehaviourを実装している必要があります。

では天気予報APIを使用して、明日の天気を取得する関数であるget_weather/1を作りましょう。

get_weather/1は都市IDを受け取り、{:ok, 明日の天気}{:error, エラー文}を出力するものとします。

この要件に沿ってBehaviourを設定すると次のようになります。

defmodule Weather.Behaviour do
  @callback get_weather(integer()) :: {:ok, String.t()} | {:error, String.t()}
end

Behaviourを実装した関数を作成する。

(1)で定義したBehaviourを実装した関数を作成します。

用語

  • @behaviour どのBehaviourを実装するかを指定
  • @impl どのBehaviourの関数を実装するかを指定
defmodule Weather.Impl do
  alias Weather.Behaviour
  @behaviour Behaviour

  @impl Behaviour
  def get_weather(city_id) do
    case Req.get!("https://weather.tsukumijima.net/api/forecast?city=#{city_id}").body do
      %{"error" => msg} ->
        {:error, msg}
      %{"forecasts" => forecasts} ->
        {:ok, Enum.at(forecasts, 1)["detail"]["weather"]}
    end
  end
end

実装をカプセル化する

Mock化するためには、Mock化する関数の処理を結果のみが帰ってくるようにする必要があります。

つまり次のようなものとする必要があります。

def Mock関数(引数) do
  # 値のみを返す
end

Mockのexpectをするためには、関数の処理を単純化する必要があります。
そのために実装のカプセル化を行い、外部APIの呼び出しを隠蔽します。

カプセル化を行うと以下のようになります。

defmodule Weather do
  def get_weather(city_id) do
    weather_impl().get_weather(city_id)
  end

  defp weather_impl() do
    Application.get_env(:weather, :call)
  end
end

ここでは外部依存性を減らすために、config.exから実装を呼び出しています。

ここでconfig.exファイルには以下を追記します。

import Config

# 追加
config :weather, :call, Weather.Impl

MockをBehaviourに基づいて設定する。

Mockを定義するには/test/test_helper.exsに以下を追記します。

/test/test_helper.exs
Mox.defmock(WeatherBehaviourMock, for: Weather.Behaviour)
Application.put_env(:weather, :call, WeatherBehaviourMock)

Mox.defmockでBehaviourを元に、Mockを定義しています。

またApplication ~の行で、test内ではWeatherはWeatherBehaviourMockを呼び出します。

テストを作成し、Mockのexpectを設定する

では実際にテストを作成します。

テストを作成する際に次の3つを記述します。

  • use ExUnit.Case
  • import Mox
  • setup :verify_on_exit!

詳しくはこちら
https://hexdocs.pm/mox/Mox.html#verify_on_exit!/1

Moxを使用したテストの記述例が次のようになります。

defmodule WeatherTest do
  use ExUnit.Case

  import Mox

  setup :verify_on_exit!

  test "get_weather/1" do
    expect(WeatherBehaviourMock, :get_weather, fn city_id ->
      assert city_id == 400040

      {:ok, "はれ ときどき ぶた 所により ビーム"}
    end)

    assert {:ok, _} = Weather.get_weather(400040)
  end
end

通常Weather.get_weather(400040)は、Weather.Impl.get_weatherで実行しているため実行した日などによって出力結果が異なります。

しかしここではWeatherBehaviourMock.get_weatherを呼び出しており、戻り値はexpectで設定した値が出力されています

したがって、特定の出力結果の場合のテストが可能になるということです。

結論

外部APIを使用する処理はMoxでテストを行う。

なぜなら、テストをする際に、特定の値が帰ってきたときのテストが記述できるから。

以上

4
1
1

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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?