8
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?

ElixirAdvent Calendar 2024

Day 17

Elixirで簡単HTTPモック: ExVCRの基本と使い方

Posted at

はじめに

本記事では、Elixir 開発者の「HTTP クライアントのテストをもっと簡単にしたい」という悩みに応える ExVCR を紹介します。

ExVCR は、テスト時に実際の HTTP リクエストを録画・再生し、ネットワークアクセスなしで再現性のあるテストを可能にする強力なツールです。
以下のような場面で役立ちます。

  • 外部 API の応答を再現したい
  • ネットワーク障害時にもテストを実行したい
  • 予期せぬ API の変更を検出したい

ExVCR の基本

ExVCRは、Ruby の VCR にインスパイアされた Elixir 用 HTTP モックライブラリです。
HTTP リクエストとそのレスポンスを録画 (record)・再生 (replay) することで、再現性のあるテストを簡単に実現します。

主な特徴は以下のとおりです。

  • カセットベースのアプローチ: リクエストとレスポンスを「カセット」として保存し、必要に応じて再生
  • 多様な HTTP ライブラリをサポート: FinchHTTPoison:httpc など複数のライブラリに対応
  • 設定可能なフィルタリング: 秘密情報(例: API キー)をマスキング可能
  • 簡単なセットアップとカスタマイズ: デフォルトの設定でも即使用可能

同様の状況での代替アプローチ

  • 手動でモック: 手動でモックを作成して HTTP クライアントの挙動を模倣する方法です。モックを使用すると、外部 API への依存を取り除くことができますが、リクエストやレスポンスの詳細を正確に再現するのが難しい場合があります。
  • Bypass を利用: Bypass を使うと、テスト用にローカルサーバーを立ち上げ、リクエストをキャプチャして処理を模倣できます。これにより、テストをよりリアルに近づけることができますが、セットアップが煩雑になりがちです。
  • リアル API との統合テスト: 実際のエンドポイントを利用してテストすることで、外部 API の挙動をそのまま確認できます。ただし、この方法はネットワーク状態に依存し、API が不安定な場合や変更があった場合にテストが失敗するリスクがあります。

これらの方法はそれぞれ一長一短がありますが、管理や保守の手間を考慮すると、ExVCRが非常に便利です。
ExVCRを利用すれば、リクエストとレスポンスを録画して保存しておくだけで、簡単にテストを再現できます。

HTTP リクエストとレスポンスは JSON 形式で保存されます。デフォルトではvcr_cassettesディレクトリに自動保存されます。一方、手動で管理したい場合はcustom_cassettesディレクトリを利用することができます。vcr_cassette_library_dirおよびcustom_cassette_library_dirの設定項目を使って保存先を変更することも可能です。

踏み出せば、その一足が道となる。

まずはExVCRのセットアップに挑戦してみましょう!

インストールとセットアップ

インストール

以下を mix.exsdeps に追加します。

defp deps do
  [
    {:exvcr, "~> 0.15.2", only: :test},
    {:req, "~> 0.5.6"} # HTTPクライアント用
  ]
end

次に、依存関係を取得します。

mix deps.get

初期設定

ExVCRはデフォルト設定のままで十分に動作します。初めて使用する場合は追加設定を行わず、そのまま動作確認を進めて問題ありません。

特定の要件でデフォルトの動作を上書きしたい場合のみ、config/config.exs に手動設定が必要です。

例えば、デフォルトでは、カセットはプロジェクトのルートディレクトリ直下に保存されますが、保存先を明示的にtest/fixture/vcr_cassettesに変更する場合には以下のように設定します。

import Config

config :exvcr,
  vcr_cassette_library_dir: "test/fixture/vcr_cassettes"

詳細は、公式ドキュメントをご参照ください。

シンプルな例

例として、https://jsonplaceholder.typicode.com のダミー API からデータを取得するテストを作成します。

API クライアントモジュール

defmodule HelloExvcr.ApiClient do
  @api_base_url "https://jsonplaceholder.typicode.com"

  def get_todo(id) do
    Req.get!("#{@api_base_url}/todos/#{id}")
  end
end

テストモジュール

defmodule HelloExvcr.ApiClientTest do
  use ExUnit.Case
  use ExVCR.Mock, adapter: ExVCR.Adapter.Finch

  setup do
    ExVCR.Config.cassette_library_dir("test/fixture/vcr_cassettes")
    :ok
  end

  test "get_todo" do
    use_cassette "get_todo_example" do
      {:ok, response} = HelloExvcr.ApiClient.get_todo(1)
      assert response.body["title"] == "delectus aut autem"
    end
  end
end

最初のテスト実行でカセットが作成され、その後はカセットを使ったテストが実行されます。

応用例

カスタムマッチャー

ExVCRでは、デフォルトのリクエスト比較条件に加えて、カスタムマッチャーを使用して特定のニーズに応じた比較ロジックを設定できます。たとえば、クエリパラメータや特定のヘッダーに基づいてリクエストを区別する場合に便利です。これにより、より柔軟なテストケースの記述が可能になります。

defmodule HelloExvcr.ApiClient do
  @api_base_url "https://jsonplaceholder.typicode.com"

  def get_todo_with_params(id, params) do
    url = "#{@api_base_url}/todos/#{id}"
    Req.get!(url, params: params)
  end
end

defmodule HelloExvcr.ApiClientTest do
  use ExUnit.Case
  use ExVCR.Mock, adapter: ExVCR.Adapter.Finch

  test "get_todo_with_custom_matcher" do
    use_cassette "custom_matcher_example", match_requests_on: [:query, :headers] do
      response =
        HelloExvcr.ApiClient.get_todo_with_params(1, %{
          "filter" => "completed",
          "userId" => 123
        })

      assert response.status == 200
      assert response.body["title"] == "delectus aut autem"
    end
  end
end

秘密情報の除外

API キーやアクセストークンなどの秘密情報をテストカセットに記録しないようにするために、フィルタリング機能を利用できます。この機能では、指定したパターンをプレースホルダーに置き換えることで、セキュリティを確保しながらカセットを共有できます。

以下の設定例では、API_TOKENという環境変数の値を<API_TOKEN>に置き換えます。

config :exvcr,
  filter_sensitive_data: [
    [pattern: System.fetch_env!("API_TOKEN"), placeholder: "<API_TOKEN>"]
  ]

リクエストボディやヘッダーの比較

ExVCRでは、リクエストボディやヘッダーの内容を細かく比較する設定が可能です。これにより、同じエンドポイントに対して異なるパラメータやヘッダーで行われたリクエストを区別できます。この機能は、複雑な API のテストで非常に役立ちます。

具体例として、以下のようにカスタムロジックを組み込むことで、比較条件を詳細に定義できます。

use_cassette "example", match_requests_on: [:query, :headers] do
  response = HelloExvcr.ApiClient.get_todo_with_params(1, %{filter: "completed"})
  assert response.status == 200
end

カスタムカセット

ExVCR では、用途に応じて複数のカセット保存先を設定し、個別に管理することができます。
例えば、テストケースごとに異なるディレクトリにカセットを保存する場合、以下のようにカスタムカセットディレクトリを指定できます。

config :exvcr,
  custom_cassette_library_dir: "test/fixture/custom_cassettes"

テストモジュール内では以下のように使用します。

use_cassette "custom_cassettes/api_client/test_specific_case" do
  response = HelloExvcr.ApiClient.get_todo(123)
  assert response.status == 200
end

この設定では、test/fixture/custom_cassettes/api_client/test_specific_case.jsonにカセットが保存されます。

おわりに

ExVCRを使うことで、HTTP テストが驚くほど簡単になります。本記事を参考に、テストの効率化に挑戦してみてください。

この記事のコードは GitHub リポジトリで公開しています。以下をご覧ください:

何か氣づいた点があれば、コメントで共有していただけると嬉しいです。

toukon-qiita-macbook_20230912_091808.jpg

8
1
0

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
8
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?