parroty / exvcr Goto Github PK
View Code? Open in Web Editor NEWHTTP request/response recording library for elixir, inspired by VCR.
License: MIT License
HTTP request/response recording library for elixir, inspired by VCR.
License: MIT License
Hey thanks for bringing VCR to Elixir!
I noticed that if I have tests that use a VCR cassette that other tests in the module that hit the same URL are being intercepted by the mocks even though they aren't wrapped in use_cassette blocks.
For example https://github.com/adamkittelson/simplex/blob/feature/metadata-credentials/test/simplex_test.exs#L72 and https://github.com/adamkittelson/simplex/blob/feature/metadata-credentials/test/simplex_test.exs#L54 both cause https://github.com/adamkittelson/simplex/blob/feature/metadata-credentials/lib/simplex/config.ex#L50 to be executed. If the test without a cassette happens to be executed after the one with the cassette it will be intercepted by the mock causing the test to fail.
I'm not sure if this is a bug or just the way VCR works. I've worked around it for now by calling :meck.unload/1 after the assertions using the vcr cassette.
I had issues using ExVCR because of transformations that have been made on the request / responses to save them. The JSON deserialization made all the strings as Elixir's String
(no character list anymore) and my oauth lib (tim/erlang-oauth
), expected a character list :-( Do you see where my problem is?
Why not using an Erlang representation instead of JSON? You'll avoid many conversions...
I tried this idea here if you're interested: https://github.com/nicoolas25/exvcr
Thank you for this work. With my minor adjustments it is very usefull!
One of the features I'm missing (that I was using all the time in Ruby and in other languages) is the ability to load a response from an existing HTTP response fixture.
I wrote a super simple helper in a client I'm working on right now that allows you to use an existing fixture as a cassette:
use_cassette :stub, ExvcrUtils.response_fixture("auth/whoami/success.http", [url: "~r/\/v2\/whoami/$"]) do
# ...
end
This was inspired by how Webmock behaves in Ruby. You can pass a full HTTP response as string to to_return
, and it will be parsed.
stub_request(:post, %r[/v2/#{account_id}/domains$])
.to_return(" ... ")
rather than having to pass each response component separately (code, headers, body).
Would you be interested in having something similar built into ExVCR? If that's the case, I can provide a PR with the patch.
It could be directly integrated in prepare_stub_record
: if the value of options[:response]
exists and it is a string, then parse it. I would delegate to the caller the responsibility to load the response (from file, a variable, or somewhere else).
Does it make sense to you?
Could you update the dep of httpotion to {:httpotion, "~> 2.1.0"}
Hi,
If I set the cassette_library_dir directly into a module or inside a Mix.Config
, the new directory is used when running the tests; however, it seems it is disregarded by the "mix" tasks.
I have set "preferred_cli_env:". Do you think I'm missing something or it is a general issue?
Everything seems fine by using the default dir - "fixture/vcr_cassettes". I'm trying to set it globally to "test/fixtures/vcr_cassettes".
It looks like filter_sensitive_data will filter header values pretty well, but what about an option to filter out certain headers from the response?
Something like:
ExVCR.Config.remove_response_headers(["Content-Type", "Content-Length", "X-Random-Custom-Header"])
When doing a request using hackney I get the following error:
** (FunctionClauseError) no function clause matching in anonymous fn/1 in ExVCR.Adapter.Hackney.Converter.sanitize_options/1
stacktrace:
lib/exvcr/adapter/hackney/converter.ex:35: anonymous fn(:with_body) in ExVCR.Adapter.Hackney.Converter.sanitize_options/1
(elixir) lib/enum.ex:1184: Enum."-map/2-lists^map/1-0-"/2
lib/exvcr/adapter/hackney/converter.ex:29: ExVCR.Adapter.Hackney.Converter.request_to_string/1
lib/exvcr/adapter/hackney/converter.ex:6: ExVCR.Adapter.Hackney.Converter.convert_to_string/2
lib/exvcr/handler.ex:137: ExVCR.Handler.get_response_from_server/2
(hackney) :hackney.request(:get, "...", [...], "", [:with_body, {:max_body, 30000000}])
Error is happening because the function sanitize_options/1
, here, is expecting a list of tuples, but hackney also accepts single atoms as values.
Hi,
I tried to use ExVCR
with Pavlov
.
Both works separately but together i always get something like function adapter_method/0 undefined
Here is a code example:
defmodule MyModuleTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Pavlov.Case, async: true
import Pavlov.Syntax.Expect
describe « .something" do
context "given valid params » do
it "returns response body » do
use_cassette "sucess » do
response = MyModule.test
expect response |> not_to_eq %{}
end
end
end
end
end
Any idea what i'm missing ?
I'm seeing the following using elixir 1.4:
warning: variable "adapter_method" does not exist and is being expanded to "adapter_method()", please use parentheses to remove the ambiguity or change the variable name
This happens for all of my tests which use exvcr. I have them setup as follows:
defmodule SomeApp.ModuleTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
setup_all do
HTTPoison.start
end
test "some test that does an api call" do
assert something == result_of_call
end
end
My deps:
defp deps do
[{:phoenix, "~> 1.2.1"},
{:phoenix_pubsub, "~> 1.0"},
{:phoenix_ecto, "~> 3.0"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.8"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},
{:cowboy, "~> 1.0"},
# App specific deps
{:comeonin, "~> 3.0"},
{:credo, "~> 0.7", only: [:dev]},
{:csv, "~> 1.4.2"},
{:dialyxir, "~> 0.3.5", only: :dev},
{:distillery, "~> 1.0", runtime: false, warn_missing: false},
{:edeliver, "~> 1.4.0"},
{:excoveralls, "~> 0.5", only: :test},
{:ex_doc, "~> 0.15", only: :dev, runtime: false},
{:ex_machina, "~> 1.0", only: :test},
{:exvcr, "~> 0.8", runtime: false},
{:gen_smtp, "~> 0.11.0"},
{:guardian, "~> 0.14.2"},
{:httpoison, "~> 0.11.0"},
{:timex, "~> 3.0"},
{:sweet_xml, "~> 0.6.2"},
{:wallaby, "~> 0.14.0"},
{:xml_builder, "~> 0.0.6"},
{:uuid, "~> 1.1"}
]
end
I am having some problems in integrating exvcr with espec.
I did not find a clear doc. on how to integrate and adapting what provided by the documentation
Is ESpec supported as testing framework ?
Right now this happens:
use_cassette/2
macro,use_cassette/2
macro,This can be seen in this PR: beam-community/stripity-stripe#80
Here's a failing travis build: https://travis-ci.org/robconery/stripity-stripe/builds/153164601
The original VCR has an ignore_hosts option, any requests made to those hosts will bypass VCR completely. Is there an equivalent option or workaround that could be used to achieve the same?
Hello,
The situation is that prior to performing request under test library must obtain a token from the service provider (do an OAuth authentication), which basically means that there will always be 2 requests.
Is it possible to ignore this first request or mock it within same cassette?
It seems like line 111 of handler.ex raises an exception when fetching a response and the cassette file already exists.
This prevents the recording of multiple different requests inside a single cassette. Is this by design?
I use the following naming for my cassettes:
use_cassette "Stripe.PlanTest/count" do
The above means I want to keep count.json
inside Stripe.PlanTest
folder, which is a name for my test module.
However VCR down cases the name of a folder to: stripe.plantest
. Can this be changed to not perform downcase on write?
Any plans to support chatterbox for recording HTTP/2 requests?
I'm wondering if there's any ability to add a delay before ExVCR sends its response to the HTTP Client?
I'm trying to track down a bug where I'm expecting a timeout to happen, but the timeout is not occurring -- it would be helpful to replicate this situation using an ExVCR cassette where I could have ExVCR not respond for some configurable amount of time.
I glanced through the ExVCR code and didn't see anywhere obvious where this sort of functionality was already in place, so I'm wondering if adding it would be useful for other people? I also understand trying to trigger timeouts really isn't ExVCR's job, so I'm unsure if adding that feature would be a bad idea, but I thought I might take a crack at it and send a PR otherwise.
My app polls a URL which returns "pending" until it returns "done". The URL is exactly the same each time I poll. Is it possible for exvcr to record the fact that the first request and the last request should send different responses?
There seems to be an issue with the Regex pattern matching in the handler.ex's matching
in which URLs that contain query paramters aren't able to be used (params after the ? will break the match). Here's an example:
test "handles failure" do
use_cassette "failure", custom: true do
case HTTPoison.post("https://myurl.com?auth_token=123abc", "", [], []) do
{:ok, %HTTPoison.Response{status_code: 204}} ->
Logger.debug("Success")
{:error, %HTTPoison.Error{id: _, reason: reason}} ->
Logger.error("Failure")
end
end
end
failure.json:
[
{
"request": {
"body": "",
"headers": {
"Content-Type": "application/json"
},
"method": "post",
"options": {
"connect_timeout": 5000
},
"url": "https://myurl.com?auth_token=123abc"
},
"response": {
"body": "",
"headers": {
},
"status_code": 400,
"type": "error"
}
}
]
A work-around is to use regex in the JSON to ignore the query params, or to simply leave the query params off:
failure.json:
[
{
"request": {
"body": "",
"headers": {
"Content-Type": "application/json"
},
"method": "post",
"options": {
"connect_timeout": 5000
},
"url": "https://myurl.com.+"
},
"response": {
"body": "",
"headers": {
},
"status_code": 400,
"type": "error"
}
}
]
But, this can be problematic if you need to ensure the query params are set correctly. Adding a Regex.escape (see below) seems to resolve the issue for this case, but looks like it breaks other pattern matching in URLs within the JSON:
pattern = Regex.compile!("^#{Regex.escape(response[:request].url)}.*$")
I'm working on a test for an API that is stateful and does not always return the same response for the same request. The code I am testing makes the same request multiple times expecting different responses because the API behaves that way, but ExVCR is getting in the way. When the 2nd request happens, it appears to playback the response for the 1st request, even though the cassette does not even exist when I run my test--at least, my test is failing due to getting an identical response as was received on the first request, and the recorded cassette contains only one interaction.
To provide a more concrete example, think about an banking API that provides /current-balance
and /send-payment
endpoints. One can imagine writing a test that does this sequence:
/current-balance
and gets the current balance./send-payment
with n
dollars./current-balance
and asserts the new balance is original_balance - n
.Can ExVCR be fixed so that no playback happens when the cassette did not exist at the point the test began, no playback occurs? Given that APIs can be stateful it is often necessary to make the same request multiple times as part of a test, expecting different responses.
I have a somewhat complicated HTTPoision i.e :hackney POST i'm doing:
HTTPoison.post!("/photos", {:multipart, [{:file, "path/to/file", { ["form-data"], [name: "\"photo\"", filename: "\"path/to/file\""]},[]}]}, [], [recv_timeout: 30000])
and this works when I run the test without VCR, but when I run through VCR I get this error:
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:multipart, [{:file, "/path/to/file", {["form-data"], [name: "\"photo\"", filename: "\"/path/to/file\""]}, []}]}
stacktrace:
(elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir) lib/string/chars.ex:17: String.Chars.to_string/1
lib/exvcr/adapter/hackney/converter.ex:6: ExVCR.Adapter.Hackney.Converter."parse_request_body (overridable 1)"/1
lib/exvcr/adapter/hackney.ex:40: ExVCR.Adapter.Hackney.generate_keys_for_request/1
lib/exvcr/handler.ex:22: ExVCR.Handler.get_response_from_cache/2
lib/exvcr/handler.ex:13: ExVCR.Handler.get_response/2
(hackney) :hackney.request(:post, ...}, [recv_timeout: 30000])
(httpoison) lib/httpoison/base.ex:394: HTTPoison.Base.request/9
...
Without knowing much of the code, maybe instead of to_string
we use IO.inspect
here https://github.com/parroty/exvcr/blob/master/lib/exvcr/adapter/hackney/converter.ex#L61
I have a cassette with two request-response pairs, and the two requests have similar URLs (the first request doesn't contain query params, the second does), however, exvcr seems to always match the first request.
So, something like this in my cassette's json:
{
"request": {
"url": "http://test"
},
"response": {
"body": "no url params"
}
},
{
"request": {
"url": "http://test?param=value
},
"response": {
"body": "got url params"
}
}
When running the test, the response body for two subsequent calls is
http://test -> no url params
http://test?param=value -> no url params
It seems to me that use_cassette
should default to using the options match_requests_on: [:query, :request_body]
. If you are submitting different data, it's likely that the server will respond differently. exvcr
implicitly hides those different responses, which is both unexpected and likely to lead to false test coverage.
I'm suggesting that the options for match_requests_on: [:query, :request_body]
should be opt-out instead of opt-in. At the very least, there should be a global config option to override this behavior, but I'd rather push people in the right direction by default.
I'm happy to write some code for this, but I wanted to get your response before starting anything.
I've been using the use_cassette :stub, [status_code: 500]
option for a while and it works great for GET
requests. When I moved to using a POST
however, the stub
doesn't seem to be catching anything.
Things I've tried:
post
method as "POST"
or "post"
I'm using the Hackney adapter and the request is going through HTTPoison. Any ideas would be really helpful. Thanks!
I want to set a custom folder for the cassettes and can do this in the setup_all
macro in my tests which is fine, but if I want to run the mix tasks I need to supply my custom dirs every time I run it - even though it is something that never changes.
Is it possible to set the directories in my config.exs
file? That way I can create a test.exs
that gets included from config.exs
when in the test env and it is available for both the mix tasks and the test runner.
Thanks.
Seems to be the same issue as #30 but for ibrowse instead of hackney.
We've been using ExVCR for a while and for the most part it's working well for us. However, I've spent a few hours using it in a certain situation where it's not working correctly at all and is producing inconsistent results. Here's our setup:
barbosa_client
(a simple HTTP client for an internal service using httpoison) and api
(a phoenix app).barbosa_client
api
, we have a phoenix channel that:
get_data
eventbarbosa_client
. The others read data off the file system or from a SQL DB)I've tried to use ExVCR in the test of the phoenix channel in api
and it's not working right at all. Specifically, the results are inconsistent -- I ran it a bunch of times to get it to record the interaction and I got several different results. Eventually, I got it to record, but now it plays back inconsistently. Occasionally (less than 10%) of the time, but the rest of the time it gets one of a few different failures.
Here are the different failures I see (both when recording and when trying to play back). Most common is this:
1) test sends the expected responses (and only the expected responses) when it receives params (DeloreanAPI.KeywordAnalysisChannelAcceptanceTest)
apps/api/test/acceptance/keyword_analysis_channel_test.exs:27
** (EXIT from #PID<0.14215.0>) killed
Some process exited, but it provides no detail to understand what happen, unfortunately. I occasionally see this:
** (EXIT from #PID<0.1154.0>) an exception was raised:
** (RuntimeError) {:error, "BarbosaClient.difficulty_for(\"rspec before\", \"google.en-US\") post error (reason: %HTTPoison.Error{id: nil, reason: :req_not_found}); url: http://[REDACTED]/barbosa-internal-api/0.0.1/keyword/difficulty, request_body: %{engine: \"google\", keyword: \"rspec before\", locale: \"en-US\"}, headers: [{\"Content-type\", \"application/json\"}]"}
(rankings_endpoint_models) lib/keyword_analysis/keyword_difficulty.ex:12: Delorean.RankingsEndpointModels.KeywordAnalysis.KeywordDifficulty.get/1
(api) web/channels/keyword_analysis_channel.ex:120: anonymous fn/5 in DeloreanAPI.KeywordAnalysisChannel.announce_and_start_with_params/3
Once I get this head-scratcher:
** (ExVCR.RequestNotMatchError) Request did not match with any one in the current cassette: /Users/myron/moz/delorean/vcr_cassettes/recorded/link_opportunities_acceptance_test.json.
Delete the current cassette with [mix vcr.delete] and re-record.
lib/exvcr/handler.ex:127: ExVCR.Handler.raise_error_if_cassette_already_exists/1
lib/exvcr/handler.ex:111: ExVCR.Handler.get_response_from_server/2
(hackney) :hackney.request(:post, "http://staging.roger.dal.moz.com/barbosa-internal-api/0.0.1/keyword/difficulty", [{"Content-type", "application/json"}], "{\"locale\":\"en-US\",\"keyword\":\"rspec before\",\"engine\":\"google\"}", [])
(httpoison) lib/httpoison/base.ex:396: HTTPoison.Base.request/9
(rest_client) lib/rest_client.ex:33: anonymous fn/5 in Delorean.RestClient.post/5
(stdlib) timer.erl:166: :timer.tc/1
(util) lib/monitor.ex:42: Delorean.Util.Monitor.track/1
(util) lib/monitor.ex:32: Delorean.Util.Monitor.perform_action_and_log/2
(barbosa_client) lib/barbosa_client.ex:25: Delorean.BarbosaClient.difficulty_for/3
(rankings_endpoint_models) lib/keyword_analysis/keyword_difficulty.ex:8: Delorean.RankingsEndpointModels.KeywordAnalysis.KeywordDifficulty.get/1
(api) web/channels/keyword_analysis_channel.ex:120: anonymous fn/5 in DeloreanAPI.KeywordAnalysisChannel.announce_and_start_with_params/3
What's odd about this is that link_opportunities_acceptance_test.json
is not used in this test -- it's used in a completely different test in a completely different file.
I've confirmed that we have async: false
in all of our tests that use ExVCR so it can't be async tests at fault (also, adding --max-cases 1
to prevent any async tests resulted in the same inconsistent weirdness).
I've tried isolating this to a simple reproducible example I can provide for you but haven't yet gotten that (sorry), but I could perhaps spend more time on that if you really need it.
I've been assuming the parallel spawned processes in the channel are at fault but (a) only one of them makes an HTTP request so there are no parallel requests happening and (b) when I move the request into a spawned process in other tests where ExVCR is working it keep working just fine...so that may be a red herring.
I did a bit of looking around the ExVCR source and noticed some places where I'd expect there to be race conditions if ExVCR was used for a test of code that makes parallel requests. I'm not sure if these are related or not, but thought I'd mention them all the same:
get
and the set
call the GenServer could respond to another message, right? I think this could be fixed by moving the logic behind a defcall
so that it all happens within the GenServer, ensuring it happens synchronously.get
and append
and I think this logic should happen within the GenServer instead of in the client process.get
gen sever calls in the map
followed by a set
at the end and again, the genserver state could change in the meantime. (Also, if the updater
or finder
functions use any GenServers they could lead to race conditions as well).To prevent race conditions, I'd expect any operation that needs to be treated atomically to happen within a single GenServer.
Thanks!
Hi,
I did find an option to filter blacklisted response headers, but I could not find any option to apply the same for request headers. For example when you have api tokens in the request headers, it would be nice to be able to filter those out of the cassette as well.
Gerard.
I'm building a web scraper using Hound, and it works fine, but when trying to test it using exvcr to mock the responses the responses are pretty much empty (at least no interesting data). I'm not sure if this is an exvcr or hound issue, or even if it is an issue with the way I have them set up, but hopefully you can help me.
Here's the code for the files I think are relevant. On the cassette, the important request is on line 68.
I have tried to use but I have had this error.
1) test post events (Keenex.Events.Test)
test/keenex/events_test.exs:12
** (CaseClauseError) no case clause matching: {:error, {:undef, [{:cover, :is_compiled, [:ibrowse], []}, {:meck_proc, :get_cover_state, 1, [file: 'src/meck_proc.erl', line: 392]}, {:meck_proc, :backup_original, 3, [file: 'src/meck_proc.erl', line: 347]}, {:meck_proc, :init, 1, [file: 'src/meck_proc.erl', line: 202]}, {:gen_server, :init_it, 6, [file: 'gen_server.erl', line: 328]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 239]}]}}
stacktrace:
src/meck_proc.erl:111: :meck_proc.set_expect/2
src/meck.erl:227: :meck.expect/3
(elixir) lib/enum.ex:537: Enum."-each/2-lists^foreach/1-0-"/2
(elixir) lib/enum.ex:537: Enum.each/2
test/keenex/events_test.exs:13
This project tests also gave the same problem.
Hi, while trying to use exvcr
with my pet project and ExTwitter
I get this:
19:53:43.564 [error] Process #PID<0.197.0> raised an exception
** (FunctionClauseError) no function clause matching in ExVCR.Adapter.Httpc.apply_filters/1
lib/exvcr/adapter/httpc.ex:56: ExVCR.Adapter.Httpc.apply_filters({:ok, #Reference<0.0.2.254>})
lib/exvcr/handler.ex:136: ExVCR.Handler.get_response_from_server/2
(inets) :httpc.request(:post, {'https://stream.twitter.com/1.1/statuses/filter.json', [], 'application/x-www-form-urlencoded', 'oauth_signature=...'}, [autoredirect: false], [sync: false, stream: :self])
(extwitter) lib/extwitter/api/streaming.ex:65: anonymous fn/3 in ExTwitter.API.Streaming.spawn_async_request/1
What am I doing wrong? If you need more details please let me know, thanks!
Just opening an issue to start discussion of this feature. Is it something you've thought of? Would you be open to a PR?
First off: great library, thank you.
I find myself wanting to filter out sensitive data in the query params of a url I'm recording AND still use match_requests_on: [:query]
. So basically I want to match on URL but substitute false values for real ones. So I could match a recording with url http://example.com?api_key=FAKE_KEY&page=1 when fetching url http://example.com?api_key=abcdefg&page=1
It would not match a cassette with url http://example.com?api_key=abcdefg&page=SOMETHING_ELSE
Is this feature something you'd adopt? Any advice? I can try to put up a PR, but don't want to waste the time if you wouldn't find it valuable.
This happens when we have multiple requests in the test suite
** (EXIT from #PID<0.3049.0>) an exception was raised:
** (ExVCR.RequestNotMatchError) Request did not match with any one in the current cassette: test/fixture/vcr_cassettes/response.json.
Delete the current cassette with [mix vcr.delete] and re-record.
Request: [:get, "https://example.com", [], "", []]
Its clear from the above exception that, there are two requests. My test suite suppose to catch the second request and match the cassette response.
Would it be possible to do following
Ignore the request response if it does not match the cassette name use_cassette "contentful-cache"
In my case, its clear that the first response would not match the my cassette response at all.... in this case, if I could do bypass the request until I find the request that matches my recorded response then we would not have inconsistent behaviour with cassettes..
I have seen multiple issues raised here
When we run the tests independently, they work fine.
Ex: mix test test/model_test.exs
However, when we run the entire test suite
Ex: mix test
we would have this issue with multiple requests not matching the cassettes.
Hi,
would it be possible to add an MIT or other license to this project?
Thanks!
Is there any plan? 😄
We like VCR, and would like to use it in our test suite. We have a suite, that is full of use_cassette
calls
Most of the time we are happy with out test suite hitting recorded response. However, right before deploy, we'd like our requests to hit a live remote service. Unfortunately, it means that we need to remove a folder with all cassettes every time we actually need to hit external service.
What we'd like to have is a configurable ability to bypass cassettes.
Options to implement this include:
make use_cassette
include a flag, say skip: true
, to skip or not skip the cassette. We then could make something like:
use_cassette "path/to/my_cassette", skip: System.get_env(:RUN_LIVE_REQUESTS) do
# ...
end
same as in item 1, only make this configurable at a higher level, e.g. in config :exvcr, skip_cassettes: true
,
have implemented both items 1 and 2, with setting for 1 overriding setting from 2.
I'm not sure if it's 100% precisely the issue, but most likely the requests to the same endpoint having different query params are considered as the same one and cache from the first response is reused for each of them.
In one of the projects I was recording cassettes for autopagination based on the links returned in headers. So I was starting with http://example.com/bookings
url and for the next page http://example.com/bookings?page=2
link was returned. However, another request wasn't performed to /bookings?page=2
but the response for /bookings
was reused, causing the infinite recursion (because http://example.com/bookings?page=2
was always returned as the link to the next page).
I'm investigating the issue closer, but I think the problem is in ExVCR.Handler.get_response_from_cache
.
Probably a stupid-simple thing I've missed, but can't get the tests to stop running in the iex -S mix
session every time I save a file… :)
Running mix test.watch
in another terminal, but this occurs even when just running iex
.
Here's the mix.exs file:
defmodule MyApp.Mixfile do
use Mix.Project
def project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.4.0",
build_embedded: Mix.env == :production,
start_permanent: Mix.env == :production,
deps: deps(),
preferred_cli_env: [
vcr: :test, "vcr.delete": :test, "vcr.check": :test, "vcr.show": :test
],
]
end
defp deps do
[
...
{:httpoison, "~> 0.11.0"},
]
++ devdeps()
++ testdeps()
end
defp devdeps do
[
...
{:mix_test_watch, "~> 0.2", only: :dev},
]
end
defp testdeps do
[
...
{:exvcr, "~> 0.8", only: [:ci, :test], runtime: false},
]
end
end
Let me know if someone need to see the config
stuff as well.
Removing exvcr
stops this behaviour.
Hi,
We're using ExVCR and we're very happy with it but sometimes we have an issue with some requests that we don't want to be intercepted.
We've looked at the README thoroughly but haven't find any clue about how to do this. We've also looked at the doc and still no clue...
Would you be interested in adding such a functionnality to ExVCR?
I guess something like an attribute that we can add in the config like an array of strings or regexes:
config :exvcr, [
# ...
ignore_urls: ["https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz", ~r(^https://\w+.iana.org/.*)]
]
We can of course do a pull request if you're ok with it, we just want to check before we invest some hours of dev. 😉
Cheers!
It seems that ExVCR.Adapter.Hackney
doesn't work with head requests.
If I use HTTPoison.head(url, headers, options)
inside of use_cassette
block, the relevant test fails with something like:
** (FunctionClauseError) no function clause matching in ExVCR.Adapter.Hackney.apply_filters/1
stacktrace:
lib/exvcr/adapter/hackney.ex:54: ExVCR.Adapter.Hackney.apply_filters({:ok, 301, [{"Server", "nginx"}, {"Date", "Thu, 24 Nov 2016 16:19:29 GMT"}, {"Content-Type", "text/html; charset=UTF-8"}, {"Connection", "keep-alive"}, {"Location", "https://dribbble.com/jobs/11990?source=feed"}, {"X-Cache", "MISS"}]})
lib/exvcr/handler.ex:136: ExVCR.Handler.get_response_from_server/2
(hackney) :hackney.request(:head, "https://somesite.com/some-path", [], "", [:insecure])
lib/httpoison/base.ex:422: HTTPoison.Base.request/9
Any ideas how this could be fixed/patched? I am presuming that should be something simple, as long as one knows the internals...
Thanks for the great library!
It'd be really useful to be able to return the value from the body of the use_cassette macro, e.g.:
defmacro use_cassette(:stub, options, test) do
quote do
stub_fixture = "stub_fixture_#{ExVCR.Util.uniq_id}"
stub = prepare_stub_record(unquote(options), adapter_method)
recorder = Recorder.start([fixture: stub_fixture, stub: stub, adapter: adapter_method])
mock_methods(recorder, adapter_method)
try do
value = unquote(test)
if options_method[:clear_mock] || unquote(options)[:clear_mock] do
:meck.unload(adapter_method.module_name)
end
value
after
# do nothing
end
end
end
If this seems reasonable, I'll submit a patch.
1) test fetch lead (ExCloseioTest)
test/ex_closeio_test.exs:11
** (ArgumentError) argument error
stacktrace:
lib/jsx.ex:154: JSX.Encoder.Tuple.json/1
lib/jsx.ex:121: JSX.Encoder.List.unzip/1
lib/jsx.ex:114: JSX.Encoder.List.json/1
lib/jsx.ex:105: JSX.Encoder.Map.unpack/2
lib/jsx.ex:105: JSX.Encoder.Map.unpack/2
lib/jsx.ex:101: JSX.Encoder.Map.json/1
lib/jsx.ex:105: JSX.Encoder.Map.unpack/2
lib/jsx.ex:101: JSX.Encoder.Map.json/1
lib/jsx.ex:126: JSX.Encoder.List.unhitch/1
lib/jsx.ex:117: JSX.Encoder.List.json/1
lib/jsx.ex:4: JSX.encode!/2
lib/exvcr/json.ex:10: ExVCR.JSON.save/2
test/ex_closeio_test.exs:15
Finished in 2.9 seconds (0.2s on load, 2.7s on tests)
1 test, 1 failure
I'm not entirely sure what the problem is here. I'm new to Elixir so I haven't really figured out a good way to debug errors that occur in the dependency chain.
I'm use HTTPoison to make the requests, they seems to working fine and returning correct responses, it looks like the error is occuring when ExVCR tries to record something.
I'm having hard time trying to define a stub for a call that has a body payload.
You can see the tests here
https://github.com/weppos/dnsimple-elixir/blob/test-create/test/dnsimple_domains_service_test.exs#L32-L51
It looks like the matcher doesn't properly match the body. Take for example this code fragment
fixture = ExvcrUtils.response_fixture("createDomain/created.http", [url: "~r/\/v2/$", request_body: ""])
use_cassette :stub, fixture do
{ :ok, response } = @service.create_domain(@client, "1010", "")
assert response.__struct__ == Dnsimple.Response
the error message is
1) test .create_domains returns a Dnsimple.Response (DnsimpleDomainsServiceTest)
test/dnsimple_domains_service_test.exs:39
** (ExVCR.InvalidRequestError) response for [URL:https://api.dnsimple.test/v2/1010/domains, METHOD:post] was not found
stacktrace:
lib/exvcr/handler.ex:28: ExVCR.Handler.get_response_from_cache/2
lib/exvcr/handler.ex:13: ExVCR.Handler.get_response/2
(hackney) :hackney.request(:post, "https://api.dnsimple.test/v2/1010/domains", [{"Accept", "application/json"}, {"User-Agent", "dnsimple-elixir/0.0.1"}, {"Authorization", "Bearer i-am-a-token"}], "", [])
(httpoison) lib/httpoison/base.ex:394: HTTPoison.Base.request/9
(httpoison) lib/httpoison.ex:66: HTTPoison.request!/5
(dnsimple) lib/dnsimple.ex:123: Dnsimple.Client.execute/6
(dnsimple) lib/dnsimple/domains_service.ex:32: Dnsimple.DomainsService.create_domain/5
test/dnsimple_domains_service_test.exs:42
It doesn't make sense at all as the request_body: ""
is there. This is actually a simplification of the real test, as I need to check the body matches the JSON-serialized version of the passed payload, but since I could not make it work I'm now trying with the simplest body possible (an empty string).
I also have one more question. Why is the request_body
mandatory when I use the stub:
stubbing mode? This is very counter-intuitive, as it doesn't work like that with cassettes.
defp match_by_request_body(response, params, options) do
if options[:stub] != nil || has_match_requests_on(:request_body, options) do
(response[:request].body || response[:request].request_body) ==
params[:request_body] |> to_string
else
true
end
end
I suggest to remove options[:stub] != nil
and just run the match when an explicit request_body
parameter is set.
When using HTTPoison
with :hackney, I got the error of response for [URL:#{params[:url]}, METHOD:#{params[:method]}] was not found
. The requests are not the same one as recorded in the cassettes so I guess that's the reason it's not working as expected.
So I propose when using ExVCR mockups, we have a switch as bypass
. Just like custom
, when set to true, it will ignore all the requests that are not specified in the recorded cassettes. Here's an example:
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
...hooks and other test cases...
test "it will bypass if the cassette did not match the request" do
use_cassette "no_match", custom: true, bypass: true do
HTTPoison.start
assert HTTPoison.get("http://not_match_cassette.com").status_code == 200
end
end
By combining the custom
switch and the bypass
switch, we now have the request to be passed directly to the server instead of using a pre-recorded one.
See PR #97 for details. Let me know if you have any question or concern regarding to it. Any discussion is welcome.
Hi I have tried to setup exvcr and use the example to ensure everything is working but I keep getting the following error:
** (ErlangError) erlang error: {:not_mocked, :ibrowse}
stacktrace:
src/meck_proc.erl:118: :meck_proc.set_expect/2
src/meck.erl:234: :meck.expect/3
(elixir) lib/enum.ex:651: Enum."-each/2-lists^foreach/1-0-"/2
(elixir) lib/enum.ex:651: Enum.each/2
test/lib/mail_chimp_test.exs:46: (test)
my code is as follows (the example provided by exvcr):
defmodule ExtraTurn.MailChimpTest do
use ExUnit.Case, async: false
use ExVCR.Mock
setup_all 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
test "httpotion" do
use_cassette "example_httpotion" do
HTTPotion.start
assert HTTPotion.get("http://example.com", []).body =~ ~r/Example Domain/
end
end
end
I am using the latest elixir version, 1.3.4:
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Elixir 1.3.4
and using Phoenix 1.2.1 (but I don't think that will affect anything).
I have tried using both the latest version on hex.pm and the latest github version.
Any help would be great!
defmodule AbrPayments.Api.V1.PaymentsControllerTest do
use AbrPayments.ConnCase
use ExUnit.Case, async: false
use ExVCR.Mock, adapter: ExVCR.Adapter.Httpc
setup_all do
AbrPayments.Abr.Http.start
ExVCR.Config.cassette_library_dir("fixture/vcr_cassettes", "fixture/custom_cassettes")
:ok
end
test "returns 401 if don't send token" do
conn = conn
|> get(payments_path(conn, :create))
|> doc
assert conn.status == 401
end
test "return 422 if accounts in form not user account" do
use_cassette :stub, [url: "http://localhost:3000/api/v1/accounts/for_payments", body: "Stub Response"] do
{:ok, body} = HTTPoison.get!("http://localhost:3000/api/v1/accounts/for_payments", [])
IO.inspect(body)
end
end
test "stub request works for HTTPoison" do
use_cassette :stub, [url: "http://www.example.com", body: "Stub Response"] do
response = HTTPoison.get!("http://www.example.com")
assert response.body =~ ~r/Stub Response/
assert response.headers["Content-Type"] == "text/html"
assert response.status_code == 200
end
end
end
If you do not create the custom_cassette
folder in the same level as your cassete folder, running mix vcr
will print an annoying message like this in the end:
** (ExVCR.PathNotFoundError) Specified path 'fixture/custom_cassettes' for reading cassettes was not found.
lib/exvcr/task/runner.ex:34: ExVCR.Task.Runner.find_json_files/1
lib/exvcr/task/runner.ex:23: ExVCR.Task.Runner.read_cassettes/1
lib/exvcr/task/runner.ex:17: anonymous fn/1 in ExVCR.Task.Runner.show_vcr_cassettes/1
(elixir) lib/enum.ex:604: Enum."-each/2-lists^foreach/1-0-"/2
(elixir) lib/enum.ex:604: Enum.each/2
(mix) lib/mix/cli.ex:58: Mix.CLI.run_task/2
My only configuration for exvcr
is ExVCR.Config.cassette_library_dir("fixture/vcr_cassettes")
.
IMHO, if the custom cassette folder is not found it should just be ignored.
The following test fails
test "test use_casette with HTTPotion and gzip compression" do
use_cassette "api_stackexchange_com" do
HTTPotion.start
response = :zlib.gunzip(HTTPotion.get("https://api.stackexchange.com/2.2").body)
assert response =~ "bad_parameter"
end
end
with the following error
1) test test use_casette with HTTPotion and gzip compression (DevQuotes.DataSourceControllerTest)
test/controllers/data_source_controller_test.exs:36
** (ErlangError) erlang error: :data_error
stacktrace:
:zlib.call/3
:zlib.inflate/2
:zlib.gunzip/1
lib/exvcr/json.ex:24: ExVCR.JSON.gunzip_recording/1
(elixir) lib/enum.ex:1184: Enum."-map/2-lists^map/1-0-"/2
lib/exvcr/json.ex:11: ExVCR.JSON.save/2
test/controllers/data_source_controller_test.exs:37: (test)
Any idea on how to fix this?
Is there a way to turn off these logs?
11:47:30.279 [error] Task #PID<0.2497.0> started from #PID<0.2495.0> terminating
** (ExVCR.RequestNotMatchError) Request did not match with any one in the current cassette: test/fixture/vcr_cassettes/geo_go_to_service.json.
Delete the current cassette with [mix vcr.delete] and re-record.
Request: [:post, "https://hooks.slack.com/services/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", [], {:form, [payload: "{\"text\":\"La tarea con ID 39310 esta en el estado arrived\\n\",\"channel\":\"#test_services\"}"]}, []]
lib/exvcr/handler.ex:155: ExVCR.Handler.raise_error_if_cassette_already_exists/2
lib/exvcr/handler.ex:137: ExVCR.Handler.get_response_from_server/2
(hackney) :hackney.request(:post, "https://hooks.slack.com/services/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", [], {:form, [payload: "{\"text\":\"La tarea con ID 39310 esta en el estado arrived\\n\",\"channel\":\"#test_services\"}"]}, [])
(httpoison) lib/httpoison/base.ex:402: HTTPoison.Base.request/9
(elixir) lib/task/supervised.ex:85: Task.Supervised.do_apply/2
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Function: &HTTPoison.post/2
Args: ["https://hooks.slack.com/services/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", {:form, [payload: "{\"text\":\"La tarea con ID 39310 esta en el estado arrived\\n\",\"channel\":\"#test_services\"}"]}]
11:47:30.280 [error] Task #PID<0.2496.0> started from #PID<0.2495.0> terminating
** (ExVCR.RequestNotMatchError) Request did not match with any one in the current cassette: test/fixture/vcr_cassettes/geo_go_to_service.json.
Delete the current cassette with [mix vcr.delete] and re-record.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.