Moxの使い方

# 目次
- [目次](#目次)
- [Mock化とは](#mock化とは)
- [使用するメリット](#使用するメリット)
- [基本的な使い方](#基本的な使い方)
  - [Behaviour(動作)を定義する](#behaviour動作を定義する)
  - [Behaviourを実装した関数を作成する。](#behaviourを実装した関数を作成する)
  - [実装をカプセル化する](#実装をカプセル化する)
  - [MockをBehaviourに基づいて設定する。](#mockをbehaviourに基づいて設定する)
  - [テストを作成し、Mockのexpectを設定する](#テストを作成しmockのexpectを設定する)
- [結論](#結論)

天気予報API

https://weather.tsukumijima.net/

以下をdepsに追加

```elixir
{: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**である。

https://github.com/dashbitco/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を設定すると次のようになります。
```elixir
defmodule Weather.Behaviour do
  @callback get_weather(integer()) :: {:ok, String.t()} | {:error, String.t()}
end
```

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

用語
* **@behaviour** どのBehaviourを実装するかを指定
* **@impl** どのBehaviourの関数を実装するかを指定 
```elixir
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化する関数の処理を**結果のみが帰ってくるようにする**必要があります。

つまり次のようなものとする必要があります。
```elixir
def Mock関数(引数) do
  # 値のみを返す
end
```

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

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

```elixir
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ファイルには以下を追記します。

```elixir
import Config

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

## MockをBehaviourに基づいて設定する。
Mockを定義するには`/test/test_helper.exs`に以下を追記します。

```elixir:/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を使用したテストの記述例が次のようになります。

```elixir
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でテストを行う。**


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

以上