Unit Testing HTTP Client error response

Mock is a simple library that allows you to test different methods responses by mocking the module. Here what it could look like to your example:

defmodule ShowMockLibraryTest do
  use ExUnit.Case

  alias HTTPoison
  import Mock

  defmodule Client do
    def perform_request(query) do
      url = "http://www.some-service?q=#{query}"

      case HTTPoison.get(url) do
        {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
          {:ok, body}

        {:error, %HTTPoison.Error{reason: reason}} ->
          {:error, reason}
      end
    end
  end

  @ok_response %HTTPoison.Response{
    status_code: 200,
    body: "Some content"
  }

  @error_response %HTTPoison.Error{reason: "Some reason"}

  test "should return the body if request returns 200" do
    with_mock HTTPoison, get: fn _ -> {:ok, @ok_response} end do
      assert {:ok, "Some content"} = Client.perform_request("some-query")
    end
  end

  test "should return the reason if request returns an error" do
    with_mock HTTPoison, get: fn _ -> {:error, @error_response} end do
      assert {:error, "Some reason"} = Client.perform_request("some-query")
    end
  end

  test "should add the query to the url before calling it" do
    with_mock HTTPoison, get: fn _ -> {:ok, @ok_response} end do
      assert {:ok, _body} = Client.perform_request("some-query")
      assert_called(HTTPoison.get("http://www.some-service?q=some-query"))
    end
  end

  test "should raise if any other response is received" do
    with_mock HTTPoison, get: fn _ -> {:error, "Unknown Error"} end do
      assert_raise CaseClauseError, fn ->
        Client.perform_request("some-query")
      end
    end
  end
end


If you use Tesla as your client, you can use their built-in mocks. See Tesla.Mock.

Another possible approach is to separate concerns in differents methods. You basically isolate code you can’t control from code that you can. This is what I mean:

defmodule SeparateConcernsTest do
  use ExUnit.Case

  alias HTTPoison

  defmodule Client do
    def perform_request(query) do
      query
      |> url()
      |> HTTPoison.get()
      |> handle()
    end

    def url(query), do: "http://www.some-service?q=#{query}"

    def handle({:ok, %HTTPoison.Response{status_code: 200, body: body}}) do
      {:ok, body}
    end

    def handle({:ok, %HTTPoison.Error{reason: reason}}), do: {:error, reason}
  end

  @ok_response %HTTPoison.Response{
    status_code: 200,
    body: "Some content"
  }

  @error_response %HTTPoison.Error{reason: "Some reason"}

  test "should return the body if request returns 200" do
    assert {:ok, "Some content"} = Client.handle({:ok, @ok_response})
  end

  test "should return the reason if request returns an error" do
    assert {:error, "Some reason"} = Client.handle({:error, @error_response})
  end

  test "should add the query to the url" do
    assert Client.url("some-query") == "http://www.some-service?q=some-query"
  end

  test "should raise if any other response is received" do
    assert_raise CaseClauseError, fn ->
      Client.handle({:error, "Unknown Error"})
    end
  end
end