SkillAgentSearch skills...

Exvcr

HTTP request/response recording library for elixir, inspired by VCR.

Install / Use

/learn @parroty/Exvcr
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ExVCR

Build Status Coverage Status hex.pm version hex.pm downloads License

Record and replay HTTP interactions library for Elixir. It's inspired by Ruby's VCR, and trying to provide similar functionalities.

Basics

The following HTTP libraries can be applied.

  • ibrowse-based libraries.
  • hackney-based libraries.
    • HTTPoison
    • support is very limited, and tested only with sync request of HTTPoison yet.
  • httpc-based libraries.
    • erlang-oauth
    • tirexs
    • support is very limited, and tested only with :httpc.request/1 and :httpc.request/4.
  • Finch
    • the deprecated Finch.request/6 functions is not supported

HTTP interactions are recorded as JSON file. The JSON file can be recorded automatically (vcr_cassettes) or manually updated (custom_cassettes).

Notes

  • ExVCR.Config functions must be called from setup or test. Calls outside of test process, such as in setup_all will not work.

Install

Add :exvcr to deps section of mix.exs.

def deps do
  [ {:exvcr, "~> 0.11", only: :test} ]
end

Optionally, preferred_cli_env: [vcr: :test] can be specified for running mix vcr in :test env by default.

def project do
  [ ...
    preferred_cli_env: [
      vcr: :test, "vcr.delete": :test, "vcr.check": :test, "vcr.show": :test
    ],
    ...
end

Usage

Add use ExVCR.Mock to the test module. This mocks ibrowse by default. For using hackney, specify adapter: ExVCR.Adapter.Hackney options as follows.

Example with ibrowse
defmodule ExVCR.Adapter.IBrowseTest do
  use ExUnit.Case, async: true
  use ExVCR.Mock

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

  test "example single request" do
    use_cassette "example_ibrowse" do
      :ibrowse.start
      {:ok, status_code, _headers, body} = :ibrowse.send_req('http://example.com', [], :get)
      assert status_code == '200'
      assert to_string(body) =~ ~r/Example Domain/
    end
  end
end
Example with hackney
defmodule ExVCR.Adapter.HackneyTest do
  use ExUnit.Case, async: true
  use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney

  setup_all do
    HTTPoison.start
    :ok
  end

  test "get request" do
    use_cassette "httpoison_get" do
      assert HTTPoison.get!("http://example.com").body =~ ~r/Example Domain/
    end
  end
end
Example with httpc
defmodule ExVCR.Adapter.HttpcTest do
  use ExUnit.Case, async: true
  use ExVCR.Mock, adapter: ExVCR.Adapter.Httpc

  setup_all do
    :inets.start
    :ok
  end

  test "get request" do
    use_cassette "example_httpc_request" do
      {:ok, result} = :httpc.request('http://example.com')
      {{_http_version, _status_code = 200, _reason_phrase}, _headers, body} = result
      assert to_string(body) =~ ~r/Example Domain/
    end
  end
end
Example with Finch
defmodule ExVCR.Adapter.FinchTest do
  use ExUnit.Case, async: true
  use ExVCR.Mock, adapter: ExVCR.Adapter.Finch

  setup_all do
    Finch.start_link(name: MyFinch)
    :ok
  end

  test "get request" do
    use_cassette "example_finch_request" do
      {:ok, response} = Finch.build(:get, "http://example.com/") |> Finch.request(MyFinch)
      assert response.status == 200
      assert Map.new(response.headers)["content-type"] == "text/html; charset=UTF-8"
      assert response.body =~ ~r/Example Domain/
    end
  end
end

Example with Start / Stop

Instead of single use_cassette, start_cassette and stop_cassette can serve as an alternative syntax.

use_cassette("x") do
  do_something
end
start_cassette("x")
do_something
stop_cassette

Custom Cassettes

You can manually define custom cassette JSON file for more flexible response control rather than just recoding the actual server response.

  • Optional 2nd parameter of ExVCR.Config.cassette_library_dir method specifies the custom cassette directory. The directory is separated from vcr cassette one for avoiding mistakenly overwriting.

  • Adding custom: true option to use_cassette macro indicates to use the custom cassette, and it just returns the pre-defined JSON response, instead of requesting to server.

defmodule ExVCR.MockTest do
  use ExUnit.Case, async: true
  import ExVCR.Mock

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

  test "custom with valid response" do
    use_cassette "response_mocking", custom: true do
      assert HTTPoison.get!("http://example.com", []).body =~ ~r/Custom Response/
    end
  end

The custom JSON file format is the same as vcr cassettes.

fixture/custom_cassettes/response_mocking.json

[
  {
    "request": {
      "url": "http://example.com"
    },
    "response": {
      "status_code": 200,
      "headers": {
        "Content-Type": "text/html"
      },
      "body": "<h1>Custom Response</h1>"
    }
  }
]

Recording VCR Cassettes

Matching

ExVCR uses URL parameter to match request and cassettes. The url parameter in the JSON file is taken as regexp string.

Removing Sensitive Data

ExVCR.Config.filter_sensitive_data(pattern, placeholder) method can be used to remove sensitive data. It searches for string matches with pattern, which is a string representing a regular expression, and replaces with placeholder. Replacements happen both in URLs and request and response bodies.

test "replace sensitive data" do
  ExVCR.Config.filter_sensitive_data("<PASSWORD>.+</PASSWORD>", "PLACEHOLDER")
  use_cassette "sensitive_data" do
    assert HTTPoison.get!("http://something.example.com", []).body =~ ~r/PLACEHOLDER/
  end
end

ExVCR.Config.filter_request_headers(header) and ExVCR.Config.filter_request_options(option) can be used to remove sensitive data in the request headers. It checks if the header is found in the request headers and blanks out it's value with ***.

test "replace sensitive data in request header" do
  ExVCR.Config.filter_request_headers("X-My-Secret-Token")
  use_cassette "sensitive_data_in_request_header" do
    body = HTTPoison.get!("http://localhost:34000/server?", ["X-My-Secret-Token": "my-secret-token"]).body
    assert body == "test_response"
  end

  # The recorded cassette should contain replaced data.
  cassette = File.read!("#{@dummy_cassette_dir}/sensitive_data_in_request_header.json")
  assert cassette =~ "\"X-My-Secret-Token\": \"***\""
  refute cassette =~  "\"X-My-Secret-Token\": \"my-secret-token\""

  ExVCR.Config.filter_request_headers(nil)
end
test "replace sensitive data in request options" do
  ExVCR.Config.filter_request_options("basic_auth")
  use_cassette "sensitive_data_in_request_options" do
    body = HTTPoison.get!(@url, [], [hackney: [basic_auth: {"username", "password"}]]).body
    assert body == "test_response"
  end

  # The recorded cassette should contain replaced data.
  cassette = File.read!("#{@dummy_cassette_dir}/sensitive_data_in_request_options.json")
  assert cassette =~ "\"basic_auth\": \"***\""
  refute cassette =~  "\"basic_auth\": {\"username\", \"password\"}"

  ExVCR.Config.filter_request_options(nil)
end

Allowed hosts

The :ignore_urls can be used to allow requests to be made to certain hosts.

setup do
  ExVCR.Setting.set(:ignore_urls, [~/example.com/])
  ExVCR.Setting.append(:ignore_urls, ~/anotherurl.com/)
end

test "an actual request is made to example.com" do
  HTTPoison.get!("https://example.com/path?query=true")
  HTTPoison.get!("https://anotherurl.com/path?query=true")
end

Ignoring query params in URL

If ExVCR.Config.filter_url_params(true) is specified, query params in URL will be ignored when recording cassettes.

test "filter url param flag removes url params when recording cassettes" do
  ExVCR.Config.filter_url_params(true)
  use_cassette "example_ignore_url_params" do
    assert HTTPoison.get!(
      "http://localhost:34000/server?should_not_be_contained", []).body =~ ~r/test_response/
  end
  json = File.read!("#{__DIR__}/../#{@dummy_cassette_dir}/example_ignore_url_params.json")
  refute String.contains?(json, "should_not_be_contained")

Removing headers from response

If ExVCR.Config.response_headers_blacklist(headers_blacklist) is specified, the headers in the list will be removed from the response.

test "remove blacklisted headers" do
  use_cassette "original_headers" do
    assert Map.has_key?(HTTPoison.get!(@url, []).headers, "connection") == true
  end

  ExVCR.Config.response_headers_blacklist(["Connection"])
  use_cassette "remove_blacklisted_headers" do
    assert Map.has_key?(HTTPoison.get!(@url, []).headers, "connection") == false
  end

  ExVCR.Config.response_headers_blacklist([])
end

Matching Options

Matching against query params

By default, query params are not used for matching. In order to include query params, specify match_requests_on: [:query] for use_cassette call.

test "matching query params with match_requests_on params" do
  use_cassette "different_query_params", match_requests_on
View on GitHub
GitHub Stars745
CategoryDevelopment
Updated2mo ago
Forks132

Languages

Elixir

Security Score

95/100

Audited on Jan 23, 2026

No findings