{"id":13508297,"url":"https://github.com/parroty/exvcr","last_synced_at":"2026-02-18T09:42:13.009Z","repository":{"id":10918538,"uuid":"13218759","full_name":"parroty/exvcr","owner":"parroty","description":"HTTP request/response recording library for elixir, inspired by VCR.","archived":false,"fork":false,"pushed_at":"2025-04-12T02:16:25.000Z","size":682,"stargazers_count":743,"open_issues_count":56,"forks_count":132,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-10-05T12:39:38.672Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/parroty.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2013-09-30T15:12:57.000Z","updated_at":"2025-09-25T18:23:22.000Z","dependencies_parsed_at":"2023-02-13T22:00:28.199Z","dependency_job_id":"a606104b-264a-4bd3-a7d6-555c73475730","html_url":"https://github.com/parroty/exvcr","commit_stats":{"total_commits":529,"total_committers":86,"mean_commits":6.151162790697675,"dds":0.3100189035916824,"last_synced_commit":"9cf5dfa2993e628be1266d2a0bd6368941574101"},"previous_names":[],"tags_count":69,"template":false,"template_full_name":null,"purl":"pkg:github/parroty/exvcr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parroty%2Fexvcr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parroty%2Fexvcr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parroty%2Fexvcr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parroty%2Fexvcr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/parroty","download_url":"https://codeload.github.com/parroty/exvcr/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parroty%2Fexvcr/sbom","scorecard":{"id":720855,"data":{"date":"2025-08-11","repo":{"name":"github.com/parroty/exvcr","commit":"c9cafeab25517f865fefbd7e4a8006e1b68092a9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.7,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":4,"reason":"Found 14/30 approved changesets -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:44: update your workflow using https://app.stepsecurity.io/secureworkflow/parroty/exvcr/tests.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/tests.yml:45: update your workflow using https://app.stepsecurity.io/secureworkflow/parroty/exvcr/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/parroty/exvcr/tests.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/tests.yml:66: update your workflow using https://app.stepsecurity.io/secureworkflow/parroty/exvcr/tests.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-9fm9-hp7p-53mf"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 19 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T11:19:13.716Z","repository_id":10918538,"created_at":"2025-08-22T11:19:13.716Z","updated_at":"2025-08-22T11:19:13.716Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29575113,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T08:38:15.585Z","status":"ssl_error","status_checked_at":"2026-02-18T08:38:14.917Z","response_time":162,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T02:00:51.032Z","updated_at":"2026-02-18T09:42:07.998Z","avatar_url":"https://github.com/parroty.png","language":"Elixir","funding_links":[],"categories":["HTTP"],"sub_categories":[],"readme":"# ExVCR\n\n[![Build Status](https://github.com/parroty/exvcr/workflows/tests/badge.svg)](https://github.com/parroty/exvcr/actions)\n[![Coverage Status](https://img.shields.io/coveralls/github/parroty/exvcr)](https://coveralls.io/r/parroty/exvcr?branch=master)\n[![hex.pm version](https://img.shields.io/hexpm/v/exvcr.svg)](https://hex.pm/packages/exvcr)\n[![hex.pm downloads](https://img.shields.io/hexpm/dt/exvcr.svg)](https://hex.pm/packages/exvcr)\n[![License](https://img.shields.io/hexpm/l/exvcr.svg)](http://opensource.org/licenses/MIT)\n\nRecord and replay HTTP interactions library for Elixir.  It's inspired by\n[Ruby's VCR](https://github.com/vcr/vcr), and trying to provide similar\nfunctionalities.\n\n### Basics\n\nThe following HTTP libraries can be applied.\n\n  - [ibrowse](https://github.com/cmullaparthi/ibrowse)-based libraries.\n  - [hackney](https://github.com/benoitc/hackney)-based libraries.\n    - [HTTPoison](https://github.com/edgurgel/httpoison)\n    - support is very limited, and tested only with sync request of HTTPoison yet.\n  - [httpc](http://erlang.org/doc/man/httpc.html)-based libraries.\n    - [erlang-oauth](https://github.com/tim/erlang-oauth/)\n    - [tirexs](https://github.com/Zatvobor/tirexs)\n    - support is very limited, and tested only with `:httpc.request/1` and `:httpc.request/4`.\n  - [Finch](https://github.com/keathley/finch)\n    - the deprecated `Finch.request/6` functions is not supported\n\nHTTP interactions are recorded as JSON file. The JSON file can be recorded\nautomatically (`vcr_cassettes`) or manually updated (`custom_cassettes`).\n\n### Notes\n\n- `ExVCR.Config` functions must be called from `setup` or `test`. Calls outside\n  of test process, such as in `setup_all` will not work.\n\n### Install\n\nAdd `:exvcr` to `deps` section of `mix.exs`.\n\n```elixir\ndef deps do\n  [ {:exvcr, \"~\u003e 0.11\", only: :test} ]\nend\n```\n\nOptionally, `preferred_cli_env: [vcr: :test]` can be specified for running `mix\nvcr` in `:test` env by default.\n\n```elixir\ndef project do\n  [ ...\n    preferred_cli_env: [\n      vcr: :test, \"vcr.delete\": :test, \"vcr.check\": :test, \"vcr.show\": :test\n    ],\n    ...\nend\n```\n\n### Usage\n\nAdd `use ExVCR.Mock` to the test module. This mocks `ibrowse` by default. For\nusing `hackney`, specify `adapter: ExVCR.Adapter.Hackney` options as follows.\n\n##### Example with ibrowse\n\n```elixir\ndefmodule ExVCR.Adapter.IBrowseTest do\n  use ExUnit.Case, async: true\n  use ExVCR.Mock\n\n  setup do\n    ExVCR.Config.cassette_library_dir(\"fixture/vcr_cassettes\")\n    :ok\n  end\n\n  test \"example single request\" do\n    use_cassette \"example_ibrowse\" do\n      :ibrowse.start\n      {:ok, status_code, _headers, body} = :ibrowse.send_req('http://example.com', [], :get)\n      assert status_code == '200'\n      assert to_string(body) =~ ~r/Example Domain/\n    end\n  end\nend\n```\n\n##### Example with hackney\n\n```elixir\ndefmodule ExVCR.Adapter.HackneyTest do\n  use ExUnit.Case, async: true\n  use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney\n\n  setup_all do\n    HTTPoison.start\n    :ok\n  end\n\n  test \"get request\" do\n    use_cassette \"httpoison_get\" do\n      assert HTTPoison.get!(\"http://example.com\").body =~ ~r/Example Domain/\n    end\n  end\nend\n```\n\n##### Example with httpc\n\n```elixir\ndefmodule ExVCR.Adapter.HttpcTest do\n  use ExUnit.Case, async: true\n  use ExVCR.Mock, adapter: ExVCR.Adapter.Httpc\n\n  setup_all do\n    :inets.start\n    :ok\n  end\n\n  test \"get request\" do\n    use_cassette \"example_httpc_request\" do\n      {:ok, result} = :httpc.request('http://example.com')\n      {{_http_version, _status_code = 200, _reason_phrase}, _headers, body} = result\n      assert to_string(body) =~ ~r/Example Domain/\n    end\n  end\nend\n```\n\n##### Example with Finch\n\n```elixir\ndefmodule ExVCR.Adapter.FinchTest do\n  use ExUnit.Case, async: true\n  use ExVCR.Mock, adapter: ExVCR.Adapter.Finch\n\n  setup_all do\n    Finch.start_link(name: MyFinch)\n    :ok\n  end\n\n  test \"get request\" do\n    use_cassette \"example_finch_request\" do\n      {:ok, response} = Finch.build(:get, \"http://example.com/\") |\u003e Finch.request(MyFinch)\n      assert response.status == 200\n      assert Map.new(response.headers)[\"content-type\"] == \"text/html; charset=UTF-8\"\n      assert response.body =~ ~r/Example Domain/\n    end\n  end\nend\n```\n\n#### Example with Start / Stop\n\nInstead of single `use_cassette`, `start_cassette` and `stop_cassette` can serve as an alternative syntax.\n\n```elixir\nuse_cassette(\"x\") do\n  do_something\nend\n```\n\n```elixir\nstart_cassette(\"x\")\ndo_something\nstop_cassette\n```\n\n#### Custom Cassettes\n\nYou can manually define custom cassette JSON file for more flexible response\ncontrol rather than just recoding the actual server response.\n\n- Optional 2nd parameter of `ExVCR.Config.cassette_library_dir` method\n  specifies the custom cassette directory. The directory is separated from vcr\n  cassette one for avoiding mistakenly overwriting.\n\n- Adding `custom: true` option to `use_cassette` macro indicates to use the\n  custom cassette, and it just returns the pre-defined JSON response, instead\n  of requesting to server.\n\n\n```elixir\ndefmodule ExVCR.MockTest do\n  use ExUnit.Case, async: true\n  import ExVCR.Mock\n\n  setup do\n    ExVCR.Config.cassette_library_dir(\"fixture/vcr_cassettes\", \"fixture/custom_cassettes\")\n    :ok\n  end\n\n  test \"custom with valid response\" do\n    use_cassette \"response_mocking\", custom: true do\n      assert HTTPoison.get!(\"http://example.com\", []).body =~ ~r/Custom Response/\n    end\n  end\n```\n\nThe custom JSON file format is the same as vcr cassettes.\n\n**fixture/custom_cassettes/response_mocking.json**\n```javascript\n[\n  {\n    \"request\": {\n      \"url\": \"http://example.com\"\n    },\n    \"response\": {\n      \"status_code\": 200,\n      \"headers\": {\n        \"Content-Type\": \"text/html\"\n      },\n      \"body\": \"\u003ch1\u003eCustom Response\u003c/h1\u003e\"\n    }\n  }\n]\n```\n\n### Recording VCR Cassettes\n\n#### Matching\n\nExVCR uses URL parameter to match request and cassettes. The `url` parameter in\nthe JSON file is taken as regexp string.\n\n#### Removing Sensitive Data\n\n`ExVCR.Config.filter_sensitive_data(pattern, placeholder)` method can be used\nto remove sensitive data. It searches for string matches with `pattern`, which\nis a string representing a regular expression, and replaces with `placeholder`.\nReplacements happen both in URLs and request and response bodies.\n\n```elixir\ntest \"replace sensitive data\" do\n  ExVCR.Config.filter_sensitive_data(\"\u003cPASSWORD\u003e.+\u003c/PASSWORD\u003e\", \"PLACEHOLDER\")\n  use_cassette \"sensitive_data\" do\n    assert HTTPoison.get!(\"http://something.example.com\", []).body =~ ~r/PLACEHOLDER/\n  end\nend\n```\n\n`ExVCR.Config.filter_request_headers(header)` and\n`ExVCR.Config.filter_request_options(option)` can be used to remove sensitive\ndata in the request headers. It checks if the `header` is found in the request\nheaders and blanks out it's value with `***`.\n\n```elixir\ntest \"replace sensitive data in request header\" do\n  ExVCR.Config.filter_request_headers(\"X-My-Secret-Token\")\n  use_cassette \"sensitive_data_in_request_header\" do\n    body = HTTPoison.get!(\"http://localhost:34000/server?\", [\"X-My-Secret-Token\": \"my-secret-token\"]).body\n    assert body == \"test_response\"\n  end\n\n  # The recorded cassette should contain replaced data.\n  cassette = File.read!(\"#{@dummy_cassette_dir}/sensitive_data_in_request_header.json\")\n  assert cassette =~ \"\\\"X-My-Secret-Token\\\": \\\"***\\\"\"\n  refute cassette =~  \"\\\"X-My-Secret-Token\\\": \\\"my-secret-token\\\"\"\n\n  ExVCR.Config.filter_request_headers(nil)\nend\n```\n\n```elixir\ntest \"replace sensitive data in request options\" do\n  ExVCR.Config.filter_request_options(\"basic_auth\")\n  use_cassette \"sensitive_data_in_request_options\" do\n    body = HTTPoison.get!(@url, [], [hackney: [basic_auth: {\"username\", \"password\"}]]).body\n    assert body == \"test_response\"\n  end\n\n  # The recorded cassette should contain replaced data.\n  cassette = File.read!(\"#{@dummy_cassette_dir}/sensitive_data_in_request_options.json\")\n  assert cassette =~ \"\\\"basic_auth\\\": \\\"***\\\"\"\n  refute cassette =~  \"\\\"basic_auth\\\": {\\\"username\\\", \\\"password\\\"}\"\n\n  ExVCR.Config.filter_request_options(nil)\nend\n```\n\n#### Allowed hosts\n\nThe `:ignore_urls` can be used to allow requests to be made to certain hosts.\n\n```elixir\nsetup do\n  ExVCR.Setting.set(:ignore_urls, [~/example.com/])\n  ExVCR.Setting.append(:ignore_urls, ~/anotherurl.com/)\nend\n\ntest \"an actual request is made to example.com\" do\n  HTTPoison.get!(\"https://example.com/path?query=true\")\n  HTTPoison.get!(\"https://anotherurl.com/path?query=true\")\nend\n```\n\n#### Ignoring query params in URL\n\nIf `ExVCR.Config.filter_url_params(true)` is specified, query params in URL\nwill be ignored when recording cassettes.\n\n```elixir\ntest \"filter url param flag removes url params when recording cassettes\" do\n  ExVCR.Config.filter_url_params(true)\n  use_cassette \"example_ignore_url_params\" do\n    assert HTTPoison.get!(\n      \"http://localhost:34000/server?should_not_be_contained\", []).body =~ ~r/test_response/\n  end\n  json = File.read!(\"#{__DIR__}/../#{@dummy_cassette_dir}/example_ignore_url_params.json\")\n  refute String.contains?(json, \"should_not_be_contained\")\n```\n\n#### Removing headers from response\n\nIf `ExVCR.Config.response_headers_blacklist(headers_blacklist)` is specified,\nthe headers in the list will be removed from the response.\n\n```elixir\ntest \"remove blacklisted headers\" do\n  use_cassette \"original_headers\" do\n    assert Map.has_key?(HTTPoison.get!(@url, []).headers, \"connection\") == true\n  end\n\n  ExVCR.Config.response_headers_blacklist([\"Connection\"])\n  use_cassette \"remove_blacklisted_headers\" do\n    assert Map.has_key?(HTTPoison.get!(@url, []).headers, \"connection\") == false\n  end\n\n  ExVCR.Config.response_headers_blacklist([])\nend\n```\n\n#### Matching Options\n\n##### Matching against query params\n\nBy default, query params are not used for matching. In order to include query\nparams, specify `match_requests_on: [:query]` for `use_cassette` call.\n\n```elixir\ntest \"matching query params with match_requests_on params\" do\n  use_cassette \"different_query_params\", match_requests_on: [:query] do\n    assert HTTPoison.get!(\"http://localhost/server?p=3\", []).body =~ ~r/test_response3/\n    assert HTTPoison.get!(\"http://localhost/server?p=4\", []).body =~ ~r/test_response4/\n  end\nend\n```\n\n##### Matching against request body\n\nBy default, request body is not used for matching. In order to include request\nbody, specify `match_requests_on: [:request_body]` for `use_cassette` call.\n\n```elixir\ntest \"matching request body with match_requests_on params\" do\n  use_cassette \"different_request_body_params\", match_requests_on: [:request_body] do\n    assert HTTPoison.post!(\"http://localhost/server\", [body: \"p=3\"]).body =~ ~r/test_response3/\n    assert HTTPoison.post!(\"http://localhost/server\", [body: \"p=4\"]).body =~ ~r/test_response4/\n  end\nend\n```\n\n##### Matching against custom parameters\n\nYou can define and use your own matchers for cases not covered by the build-in\nmatchers. To do this you can specify `custom_matchers: [func_one, func_two, ...]`\nfor `use_cassette` call.\n\n```elixir\ntest \"matching special header with custom_matchers\" do\n  matches_special_header = fn response, keys, _recorder_options -\u003e\n    recorded_headers = always_map(response.request.headers)\n    expected_value = recorded_headers[\"X-Special-Header\"]\n    keys[:headers]\n    |\u003e Enum.any?(\u0026(match?({\"X-Special-Header\", ^expected_value}, \u00261)))\n  end\n\n  use_cassette \"special_header_match\", custom_matchers: [matches_special_header] do\n    # These two requests will match with each other since our custom matcher matches (even if without matching all headers)\n    assert HTTPoison.post!(\"http://localhost/server\",\n        [headers: [\"User-Agent\": \"My App\", \"X-Special-Header\": \"Value One\"]]).body =~ ~r/test_response_one/\n    assert HTTPoison.post!(\"http://localhost/server\",\n        [headers: [\"User-Agent\": \"Other App\", \"X-Special-Header\": \"Value One\"]]).body =~ ~r/test_response_one/\n\n    # This will not match since the header has a different value:\n    assert HTTPoison.post!(\"http://localhost/server\",\n        [headers: [\"User-Agent\": \"My App\", \"X-Special-Header\": \"Value Two\"]]).body =~ ~r/test_response_two/\n  end\nend\n```\n\n### Default Config\n\nDefault parameters for `ExVCR.Config` module can be specified in\n`config\\config.exs` as follows.\n\n```elixir\nuse Mix.Config\n\nconfig :exvcr, [\n  vcr_cassette_library_dir: \"fixture/vcr_cassettes\",\n  custom_cassette_library_dir: \"fixture/custom_cassettes\",\n  filter_sensitive_data: [\n    [pattern: \"\u003cPASSWORD\u003e.+\u003c/PASSWORD\u003e\", placeholder: \"PASSWORD_PLACEHOLDER\"]\n  ],\n  filter_url_params: false,\n  filter_request_headers: [],\n  response_headers_blacklist: []\n]\n```\n\nIf `exvcr` is defined as test-only dependency, describe the above statement in\ntest-only config file (ex. `config\\test.exs`) or make it conditional (ex. wrap\nwith `if Mix.env == :test`).\n\n### Global mock experimental feature\n\nThe global mock is an attempt to address a general issue with **exvcr being slow**, see [#107](https://github.com/parroty/exvcr/issues/107)\n\nIn general, every use_cassette takes around 500 ms so if you extensively use cassettes it could spend minutes doing `:meck.expect/2` and `:meck.unload/1`. Even `exvcr` tests  need 40 seconds versus 1 second when global mock is used.\n\nSince feature is **experimental** be careful when using it. Please note the following:\n\n- ExVCR implements global mock, which means that all HTTP client calls outside of `use_cassette` go through `meck.passthough/1`.\n- There are some report that the feature doesn't work in some case, see [the issue](https://github.com/parroty/exvcr/issues/159).\n- By default, the global mocking disabled, to enabled it set the following in config:\n\n```elixir\nuse Mix.Config\n\nconfig :exvcr, [\n  global_mock: true\n]\n```\n\nAll tests that are written for `exvcr` could also be running in global mocking mode:\n\n```\n$ GLOBAL_MOCK=true mix test\n\n.........................................................\n\nFinished in 1.3 seconds\n141 tests, 0 failures\n\nRandomized with seed 905427\n```\n\n### Mix Tasks\n\nThe following tasks are added by including `exvcr` package.\n\n- [mix vcr](#mix-vcr-show-cassettes)\n- [mix vcr.delete](#mix-vcrdelete-delete-cassettes)\n- [mix vcr.check](#mix-vcrcheck-check-cassettes)\n- [mix vcr.show](#mix-vcrshow-show-cassettes)\n- [mix vcr --help](#mix-vcr-help-help)\n\n#### [mix vcr] Show cassettes\n\n```shell\n$ mix vcr\nShowing list of cassettes in [fixture/vcr_cassettes]\n  [File Name]                              [Last Update]\n  example_httpoison.json                   2013/11/07 23:24:49\n  example_ibrowse.json                     2013/11/07 23:24:49\n  example_ibrowse_multiple.json            2013/11/07 23:24:48\n  httpoison_delete.json                    2013/11/07 23:24:47\n  httpoison_patch.json                     2013/11/07 23:24:50\n  httpoison_post.json                      2013/11/07 23:24:51\n  httpoison_put.json                       2013/11/07 23:24:52\n\nShowing list of cassettes in [fixture/custom_cassettes]\n  [File Name]                              [Last Update]\n  method_mocking.json                      2013/10/06 22:05:38\n  response_mocking.json                    2013/09/29 17:23:38\n  response_mocking_regex.json              2013/10/06 18:13:45\n```\n\n#### [mix vcr.delete] Delete cassettes\n\nThe `mix vcr.delete` task deletes the cassettes that contains the specified\npattern in the file name.\n\n```shell\n$ mix vcr.delete ibrowse\nDeleted example_ibrowse.json.\nDeleted example_ibrowse_multiple.json.\n```\n\nIf `-i` (`--interactive`) option is specified, it asks for confirmation before\ndeleting each file.\n\n```shell\n$ mix vcr.delete ibrowse -i\ndelete example_ibrowse.json? y\nDeleted example_ibrowse.json.\ndelete example_ibrowse_multiple.json? y\nDeleted example_ibrowse_multiple.json.\n```\n\nIf `-a` (`--all`) option is specified, all the cassettes in the specified folder\nbecomes the target for delete.\n\n#### [mix vcr.check] Check cassettes\n\nThe `mix vcr.check` shows how many times each cassette is applied while\nexecuting `mix test` tasks. It is intended for verifying  the cassettes are\nproperly used. `[Cassette Counts]` indicates the count that the pre-recorded\nJSON cassettes are applied. `[Server Counts]` indicates the count that server\naccess is performed.\n\n```shell\n$ mix vcr.check\n...............................\n31 tests, 0 failures\nShowing hit counts of cassettes in [fixture/vcr_cassettes]\n  [File Name]                              [Cassette Counts]    [Server Counts]\n  example_httpoison.json                   1                    0\n  example_ibrowse.json                     1                    0\n  example_ibrowse_multiple.json            2                    0\n  httpoison_delete.json                    1                    0\n  httpoison_patch.json                     1                    0\n  httpoison_post.json                      1                    0\n  httpoison_put.json                       1                    0\n  sensitive_data.json                      0                    2\n  server1.json                             0                    2\n  server2.json                             2                    2\n\nShowing hit counts of cassettes in [fixture/custom_cassettes]\n  [File Name]                              [Cassette Counts]    [Server Counts]\n  method_mocking.json                      1                    0\n  response_mocking.json                    1                    0\n  response_mocking_regex.json              1                    0\n```\n\nThe target test file can be limited by specifying test files, as similar as\n`mix test` tasks.\n\n```shell\n$ mix vcr.check test/exvcr_test.exs\n.............\n13 tests, 0 failures\nShowing hit counts of cassettes in [fixture/vcr_cassettes]\n  [File Name]                              [Cassette Counts]    [Server Counts]\n  example_httpoison.json                   1                    0\n...\n...\n```\n\n#### [mix vcr.show] Show cassettes\n\nThe `mix vcr.show` task displays the contents of cassettes JSON file in the\nprettified format.\n\n```shell\n$ mix vcr.show fixture/vcr_cassettes/httpoison_get.json\n[\n  {\n    \"request\": {\n      \"url\": \"http://example.com\",\n      \"headers\": [],\n      \"method\": \"get\",\n      \"body\": \"\",\n      \"options\": []\n    },\n...\n```\n\n#### [mix vcr --help] Help\n\nDisplays helps for mix sub-tasks.\n\n```shell\n$ mix vcr --help\nUsage: mix vcr [options]\n  Used to display the list of cassettes\n\n  -h (--help)         Show helps for vcr mix tasks\n  -d (--dir)          Specify vcr cassettes directory\n  -c (--custom)       Specify custom cassettes directory\n\nUsage: mix vcr.delete [options] [cassette-file-names]\n  Used to delete cassettes\n\n  -d (--dir)          Specify vcr cassettes directory\n  -c (--custom)       Specify custom cassettes directory\n  -i (--interactive)  Request confirmation before attempting to delete\n  -a (--all)          Delete all the files by ignoring specified [filenames]\n\nUsage: mix vcr.check [options] [test-files]\n  Used to check cassette use on test execution\n\n  -d (--dir)          Specify vcr cassettes directory\n  -c (--custom)       Specify custom cassettes directory\n\nUsage: mix vcr.show [cassette-file-names]\n  Used to show cassette contents\n\n```\n\n##### Notes\n\nIf the cassette save directory is changed from the default, [`-d`, `--dir`] option\n(for vcr cassettes) and [`-c`, `--custom`] option (for custom cassettes) can be\nused to specify the directory.\n\n### IEx Helper\n\n`ExVCR.IEx` module provides simple helper functions to display the HTTP\nrequest/response in JSON format, instead of recording in the cassette files.\n\n```elixir\n% iex -S mix\nErlang R16B03 (erts-5.10.4) ...\nInteractive Elixir (0.12.5) - press Ctrl+C to exit (type h() ENTER for help)\niex(1)\u003e require ExVCR.IEx\nnil\niex(2)\u003e ExVCR.IEx.print do\n...(2)\u003e   :ibrowse.send_req('http://example.com', [], :get)\n...(2)\u003e end\n[\n  {\n    \"request\": {\n      \"url\": \"http://example.com\",\n      \"headers\": [],\n      \"method\": \"get\",\n      \"body\": \"\",\n      \"options\": []\n    },\n    \"response\": {\n      \"type\": \"ok\",\n      \"status_code\": 200,\n...\n```\n\nThe adapter option can be specified as `adapter` argument of print function, as\nfollows.\n\n```elixir\n% iex -S mix\nErlang R16B03 (erts-5.10.4) ...\n\nInteractive Elixir (0.12.5) - press Ctrl+C to exit (type h() ENTER for help)\niex(1)\u003e require ExVCR.IEx\nnil\niex(2)\u003e ExVCR.IEx.print(adapter: ExVCR.Adapter.Hackney) do\n...(2)\u003e   HTTPoison.get!(\"http://example.com\").body\n...(2)\u003e end\n[\n  {\n    \"request\": {\n      \"url\": \"http://example.com\",\n...\n```\n\n### Stubbing Response\n\nSpecifying `:stub` as fixture name allows directly stubbing the response\nheader/body information based on parameter.\n\n```elixir\ntest \"stub request works for HTTPoison\" do\n  use_cassette :stub, [url: \"http://www.example.com\", body: \"Stub Response\"] do\n    response = HTTPoison.get!(\"http://www.example.com\")\n    assert response.body =~ ~r/Stub Response/\n    assert response.headers[\"Content-Type\"] == \"text/html\"\n    assert response.status_code == 200\n  end\nend\n\ntest \"stub request works for httpc\" do\n  use_cassette :stub, [url: \"http://www.example.com\",\n                       method: \"get\",\n                       status_code: [\"HTTP/1.1\", 200, \"OK\"],\n                       body: \"success!\"] do\n\n  {:ok, result} = :httpc.request('http://example.com')\n  {{_http_version, _status_code = 200, _reason_phrase}, _headers, body} = result\n  assert to_string(body) == \"success!\"\nend\n\ntest \"stub request works for Finch\" do\n  use_cassette :stub, [url: \"http://www.example.com\",\n                       method: \"get\",\n                       status_code: 200,\n                       body: \"Stub Response\"] do\n\n  {:ok, response} = Finch.build(:get, \"http://example.com/\") |\u003e Finch.request(MyFinch)\n  assert response.body =~ ~r/Stub Response/\n  assert Map.new(response.headers)[\"content-type\"] == \"text/html\"\n  assert response.status_code == 200\nend\n\ntest \"stub multiple requests works on Finch\" do\n  stubs = [\n    [url: \"http://example.com/1\", body: \"Stub Response 1\", status_code: 200],\n    [url: \"http://example.com/2\", body: \"Stub Response 2\", status_code: 404]\n  ]\n\n  use_cassette :stub, stubs do\n    {:ok, response} = Finch.build(:get, \"http://example.com/1\") |\u003e Finch.request(ExVCRFinch)\n    assert response.status == 200\n    assert response.body =~ ~r/Stub Response 1/\n\n    {:ok, response} = Finch.build(:get, \"http://example.com/2\") |\u003e Finch.request(ExVCRFinch)\n    assert response.status == 404\n    assert response.body =~ ~r/Stub Response 2/\n  end\nend\n```\n\nIf the specified `:url` parameter doesn't match requests called inside the\n`use_cassette` block, it raises `ExVCR.InvalidRequestError`.\n\nThe `:url` can be regular expression string. Please note that you should use\nthe `~r` sigil with `/` as delimiters.\n\n```elixir\ntest \"match URL with regular expression\" do\n  use_cassette :stub, [url: \"~r/(foo|bar)/\", body: \"Stub Response\", status_code: 200] do\n    # ...\n  end\nend\n\ntest \"make sure to properly escape the /\" do\n  use_cassette :stub, [url: \"~r/\\/path\\/to\\/file\\/without\\/trailing\\/slash\\/does\\/not\\/work\", body: \"Stub Response\", status_code: 200] do\n    # ...\n  end\nend\n\ntest \"the sigil delimiter cannot be anything else\" do\n  use_cassette :stub, [url: \"~r{this-delimiter-does-not-work}\", body: \"Stub Response\", status_code: 200] do\n    # ...\n  end\nend\n```\n\n### TODO\n\n- Improve performance, as it's very slow.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparroty%2Fexvcr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparroty%2Fexvcr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparroty%2Fexvcr/lists"}