アルパカログ

カスタマーサポート (CS) とエンジニアリングを掛け算したい CRE (Customer Reliability Engineer) が気になる技術や思ったことなど。

【Elixir】HTTPotionでお手軽APIクライアント作成

Elixir で API クライアントを作るときは HTTPotionmetaprogramming magic を使うと簡単です。

metaprogramming magic を使うとこんな風に書けます。

defmodule MyAPIClient do
  use HTTPotion.Base

  def process_url(url) do
    "https://api.my-site.com/" <> url
  end

  def process_request_headers(headers) do
    Keyword.put headers, :"Authorization", "Bearer xxx"
  end

  def process_response_body(body) do
    body |> Poison.decode!
  end
end
MyAPIClient.get("users").body

HTTPotion をモックするモジュール

APIクライアントを作る場面って、ローカルの開発環境では適当な API サーバがなかったり、そもそもまだ API を試せる状況じゃなかったりするんですよね。

さらに悪いことに、モックしようにも GitHub を探すと test 向けは出てくるものの、dev 向けはすぐに出てこない。

というわけで、HTTPotion の HTTP 通信を開発環境でモックする方法を紹介します。

HTTPotion の仕組みは use されたときに use した側のモジュールに関数を定義するようになっています。__using__ マクロの quote がそれです。

さらにおもしろいことに、それらの関数は全てオーバーライド可能になっているのです。

そこで下記のようなモックモジュールを定義します。

defmodule MockHTTPClient do
  @moduledoc """
  dev, testでHTTPotionによる外部HTTPリクエストをmockするmodule
  """

  @doc """
  HTTPotion.request/3 をoverrideしてdummy_responseを返します。
  """
  defmacro __using__(_) do
    quote do
      def request(method, url, options \\ []) do
        case _should_mock?() do
          false -> super(method, url, options)
          true  -> _dummy_response(method, url, options)
        end
      end

      def _should_mock? do
        ~w(dev test) |> Enum.member?(Application.get_env(:my_app, :build_env, "dev"))
      end

      def _dummy_response(method, url, option) do
        %HTTPotion.Response{
          status_code: 200,
          headers: %HTTPotion.Headers{hdrs: []},
          body: _dummy_response_body(method, url, option) |> process_response_body
        }
      end

      def _dummy_response_body(:get, "users", query: %{}) do
        %{
          users: [
            %{
              id: 1,
              name: "xxx"
            }
          ]
        } |> Poison.encode!
      end
    end
  end
end

このモジュールのミソは、HTTPotion.request/3 をオーバーライドし、環境によって dev, test ならdummy_response を、それ以外なら元の request/3 を呼んでいるところです。

このモジュールを先ほど作った API クライアントの中でかつ HTTPotion.Base の後で use します。

defmodule MyAPIClient do
  use HTTPotion.Base
  use MockHTTPClient
  ...

これで dev, test 環境では dummy_response_body に定義したデータを返すようになります。

dummy_response_body を API に合わせて作っておくことで異なるレスポンスデータにも対応できます。

ぜひ試してみてください。

あわせて読みたい

alpacat.hatenablog.com