parroty / exvcr

HTTP request/response recording library for elixir, inspired by VCR.
MIT License
722 stars 132 forks source link

HTTPoison response body sometimes gets recorded as a reference #109

Closed myronmarston closed 6 years ago

myronmarston commented 7 years ago

In our application we are using HTTPoison like this:

    %{status_code: 200, body: body} = HTTPoison.get!(url, [], params: %{scope: "domain"})

We have a test for this code that uses VCR. Sometimes the cassette gets recorded as:

[
  {
    "request": {
      "body": "",
      "headers": [],
      "method": "get",
      "options": [],
      "request_body": "",
      "url": "http://someapi.com/foo?scope=domain"
    },
    "response": {
      "body": "#Reference<0.0.4.6991>",
      "headers": {
        "Content-Type": "application/json; charset=utf-8",
        "Connection": "close",
        "Content-Length": "296"
      },
      "status_code": 200,
      "type": "ok"
    }
  }
]

Notice that the response body got recorded as the inspect representation of a reference. When this is recorded like this, the test passes, but then fails on playback. If I delete the cassette and re-record it, it sometimes does the correct thing and records the actual response body. Other times it records this reference. So it appears to be non-deterministic (perhaps a race condition?)

karmajunkie commented 6 years ago

+1, seeing this same behavior. The client code looks like this:

  defp get(path, args \\ []) do
    {:ok, result} = HTTPoison.get(base_url <> path, headers, options)

    result
    |> Map.get(:body)
    |> Poison.decode!
  end

  defp base_url do
    Application.get_env(:my_app, :api_url)
  end

  defp options() do
    [ssl: [{:versions, [:'tlsv1.2']}], recv_timeout: 60_000, timeout: 15_000]
  end

  defp headers() do
    %{"apiKey": Application.get_env(:my_app, :apiKey)}
  end
jameskerr commented 6 years ago

I'm also seeing this.

akrisanov commented 5 years ago

The problem still exists.

# mix.exs
defp deps do
    [
      {:httpoison, "~> 1.3"},
      {:poison, "~> 4.0"},
...

I'm trying to test following GET request:

def available_cryptocurrencies() do
  url = @api_host <> "/api/mobgetcurrencies"

  case do_get_request(url) do
    {:ok, body} ->
      coins =
        body
        |> Enum.filter(fn res -> res["isActive"] == true end)
        |> Enum.sort_by(fn res -> {res["short_name"]} end)

      {:ok, coins}

    {:error, reason} ->
      {:error, reason}
  end
end

defp do_get_request(url, headers \\ []) do
  case HTTPoison.get(url, headers) do
    {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
      case Poison.decode(body) do
        {:ok, decoded} -> {:ok, decoded}
        {:error, error} -> {:error, error}
      end

    {:ok, %HTTPoison.Response{status_code: status_code}} ->
      {:error, status_code}

    {:error, error} ->
      {:error, error}
  end
end

The test itself is pretty simple:

use ExUnit.Case, async: false
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney

setup_all do
  HTTPoison.start
end

describe "available_cryptocurrencies/0" do
  test "retrive a list of all available coins" do
    use_cassette "available_cryptocurrencies" do
      assert {:ok, _} = Indacoin.available_cryptocurrencies()
    end
  end
end

Any ideas why it can happen?

elixir --version
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [dtrace]

Elixir 1.7.3 (compiled with Erlang/OTP 21)

One more thing: the actual response is somewhat large – ~1200 resources.