Tratando dados da resposta

Você deve ter notado que a resposta do cliente so testa o status.

defmodule CoffeeShop.Integrations.Coffee.ClientTest do
  # ...
  describe "all_hot_coffees/0" do
    test "respond a list of hot coffees", %{bypass: bypass} do
      Bypass.expect(bypass, "GET", "/coffee/hot", fn conn ->
        Plug.Conn.resp(conn, 200, "")
      end)

      opts = [
        base_url: "http://localhost:3000"
      ]

      assert {:ok, %Response{status: status}} = Client.all_hot_coffees(opts)
      assert status == 200
    end
    
    # ...
  end
end

Não conseguimos fazer muita coisa apenas com o status. Precisamos de dados.

Para deixar as coisas mais estruturadas, utilizarei nesse estudo utilizarei struct onde conseguir tirar mais proveito. Você pode não precisar de tanta estrutura, isso vai depender do seu projeto.

Seguimos.

Agora precisamos obter uma resposta de nosso serviço, para usar como dado. Acessando o endpoint, conseguimos isso fácil: https://api.sampleapis.com/coffee/hot

Com o dado em mão, criamos o arquivo e adicionamos a resposta diretamente em JSON.

test/coffee_shop/integrations/coffee/fixtures/success.json
[{"title":"Caramel Latte","description":"Om du gillar latte med en speciell smak kan karamell latte vara det bästa alternativet för att ge dig en upplevelse av den naturliga sötman och krämigheten hos ångad mjölk och karamell.","ingredients":["Espresso","Ångad mjölk","Karamellsirap"],"image":"https://images.unsplash.com/photo-1599398054066-846f28917f38?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D","id":3},{"title":"Cappuccino","description":"Cappuccino är en latte som är gjord med mer skum än ångad mjölk, ofta med ett strö av kakaopulver eller kanel på toppen. Ibland kan du hitta variationer som använder grädde istället för mjölk eller sådana som tillsätter smakämnen också.","ingredients":["Espresso","Ångad mjölk","Foam"],"image":"https://images.unsplash.com/photo-1557006021-b85faa2bc5e2?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D","id":4},{"title":"Americano","description":"Med en liknande smak som svart kaffe består americano av en espresso skott utspätt med hett vatten.","ingredients":["Espresso","Hett vatten"],"image":"https://images.unsplash.com/photo-1532004491497-ba35c367d634?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D","id":5},{"title":"Espresso","description":"Ett espressoskott kan serveras ensamt eller användas som grund för de flesta kaffedrycker, som latte och macchiato.","ingredients":["Espresso"],"image":"https://images.unsplash.com/photo-1579992357154-faf4bde95b3d?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D","id":6},{"title":"Macchiato","description":"Macchiaton är en annan espresso-baserad dryck som har en liten mängd skum på toppen. Det är det glada mellanrummet mellan en cappuccino och en doppio.","ingredients":["Espresso","Foam"],"image":"https://images.unsplash.com/photo-1557772611-722dabe20327?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D","id":7},{"title":"Mocha","description":"För alla chokladälskare där ute kommer ni att bli förälskade i en mocha. Mocha är en choklad-espressodryck med ångad mjölk och skum.","ingredients":["Espresso","Ångad mjölk","Choklad"],"image":"https://images.unsplash.com/photo-1607260550778-aa9d29444ce1?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D","id":8},{"title":"Hot Chocolate","description":"Under kalla vinterdagar får en kopp varm choklad dig att känna dig bekväm och lycklig. Den får dig också att må bra eftersom den innehåller energigivande koffein.","ingredients":["Choklad","Mjölk"],"image":"https://images.unsplash.com/photo-1542990253-0d0f5be5f0ed?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NDh8fGhvdCUyMGNob2NvbGF0ZXxlbnwwfHwwfHx8MA%3D%3D","id":9},{"title":"Chai Latte","description":"Om du letar efter en smakfull varm dryck mitt i vintern, välj chai latte. Kombinationen av kardemumma och kanel ger en underbar smak.","ingredients":["Te","Mjölk","Ingefära","Kardemumma","Kanel"],"image":"https://images.unsplash.com/photo-1578899952107-9c390f1af1b7?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTJ8fGNoYWklMjBsYXR0ZXxlbnwwfHwwfHx8MA%3D%3D","id":10},{"title":"Matcha Latte","description":"Matcha latte är en grön, hälsosam kaffedryck med finkrossad matcha-te och mjölk, erbjuder mild sötma, en unik smak och en mild koffeinkick.","ingredients":["Matcha-pulver","Mjölk","Socker*"],"image":"https://images.unsplash.com/photo-1536256263959-770b48d82b0a?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8M3x8bWF0Y2hhJTIwbGF0dGV8ZW58MHx8MHx8fDA%3D","id":11},{"title":"Seasonal Brew","description":"Säsongs kaffe med olika smaktoner som karamell, frukt och choklad","ingredients":["Kaffe"],"image":"https://images.unsplash.com/photo-1611162458324-aae1eb4129a4?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTg1fHxibGFjayUyMGNvZmZlZXxlbnwwfHwwfHx8MA%3D%3D","id":12},{"title":"Svart Te","description":"Svart te föddes i Kina. Det är tillverkat av blad från en växt som kallas Camellia och kan smaksättas olika med frukter till exempel. En trevlig, varm, smakfull och aromatisk dryck som passar till vardagen.","ingredients":["Te"],"image":"https://images.unsplash.com/photo-1576092768241-dec231879fc3?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MjB8fHRlYXxlbnwwfHwwfHx8MA%3D%3D","id":13},{"title":"Islatte","description":"Iced latte är en kyld kaffedryck som görs genom att blanda espresso och kyld mjölk. Den serveras med isbitar och är även känd som cafè latte iced eller latte on the rocks.","ingredients":["Espresso","Mjölk","Is","Sirap"],"image":"https://images.unsplash.com/photo-1517701550927-30cf4ba1dba5?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NHx8aWNlZCUyMGxhdHRlfGVufDB8fDB8fHww","id":14},{"title":"Islatte Mocha","description":"Iced latte Mocha är en kombination av latte och mocha, som i sig är en kombination av choklad och kaffe. Den ger kalla dryckälskare en läcker upplevelse av choklad och kaffe.","ingredients":["Espresso","Is","Mjölk","Choklad "],"image":"https://images.unsplash.com/photo-1642647391072-6a2416f048e5?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mzh8fGljZWQlMjBtb2NoYSUyMGxhdHRlfGVufDB8fDB8fHww","id":15},{"title":"Frapino Caramel","description":"Det är en blandad eller bättre sagt skakad kaffe med vispad grädde på toppen. Ett måste för varma sommardagar.","ingredients":["coffee","Is","Mjölk","Karamellsirap","Vispgrädde*","Karamellsås"],"image":"https://images.unsplash.com/photo-1662047102608-a6f2e492411f?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NHx8ZnJhcGlubyUyMGNhcmFtZWx8ZW58MHx8MHx8fDA%3D","id":16},{"title":"Frapino Mocka","description":"Ännu en berömd och utsökt kall dryck för dem som föredrar choklad. Tänk dig smaken av en shake med choklad och vispad grädde på toppen.","ingredients":["Coffee","Is","Mjölk","Cocoa","Vispgrädde*"],"image":"https://images.unsplash.com/photo-1530373239216-42518e6b4063?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NHx8ZnJhcGlubyUyMG1vY2hhfGVufDB8fDB8fHww","id":17},{"title":"Apelsinjuice","description":"Vi har inget att säga om vår nypressade apelsinjuice. Du måste prova den själv.","ingredients":["Färska Apelsiner","Is"],"image":"https://images.unsplash.com/photo-1600271886742-f049cd451bba?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NzF8fG9yYW5nZSUyMGp1aWNlfGVufDB8fDB8fHww","id":18}]

Optei por deixar o dado dentro do contexto. Assim facilita a nomeação e utilização. Junto com entendimento e leitura.

Depois precisamos carregar esse arquivo em nosso teste e retornar a resposta dele em nosso mock.

test/coffee_shop/integrations/coffee/client_test.exs
defmodule CoffeeShop.Integrations.Coffee.ClientTest do
  use ExUnit.Case

  alias CoffeeShop.Integrations.Coffee.Response
  alias CoffeeShop.Integrations.Coffee.Client

  describe "all_hot_coffees/0" do
    # ...
    test "respond a list of hot coffees", %{bypass: bypass} do
      response =
        File.read!("test/coffee_shop/integrations/coffee/fixtures/success.json")

      Bypass.expect(bypass, "GET", "/coffee/hot", fn conn ->
        Plug.Conn.resp(conn, 200, response)
      end)

      opts = [
        base_url: "http://localhost:3000"
      ]

      assert {:ok, %Response{status: status, body: body}} = Client.all_hot_coffees(opts)
      
      assert status == 200
    end
    
    # ...
  end
end

Mockamos uma resposta usando o arquivo e atualizamos nossas assertions para garantir que algo está vindo de resposta no body.

mix test
> mix test
.....
Finished in 0.6 seconds (0.00s async, 0.6s sync)
1 doctest, 4 tests, 0 failures

Perfeito, temos um body com uma resposta no formato de string e uma estrutura JSON dentro. Para verificarmos se esta tudo certo, podemos utilizar o iex.

iex -S mix

Obtendo dados:

{:ok, %CoffeeShop.Integrations.Coffee.Response{body: body}} = CoffeeShop.Integrations.Coffee.Client.all_hot_coffees()
{:ok, 
   %CoffeeShop.Integrations.Coffee.Response{
     status: 200,
     body: "[{\"title\": \"xxxxx\", \"description\": \"zzzz\"}]"
   }
}

Pode notar que o body vem com scapes, que são essas barras antes de cada aspas duplas (\") deixando utilizar aspas duplas dentro de aspas duplas.

Como pode ser notado, temos os dados em mãos, porém temos a dificuldade de pegar um dado especifico, uma vez que ele não está identificado como estrutura de fácil acesso. Para facilitar isso, podemos decodificar o JSON transformando o dado para estruturas que o elixir consegue lidar melhor, lista e mapas.

O responsável por lidar com a resposta é nossa função Response.build/1. Vamos atualizar seu test para refletir o que queremos. Para termos um cenário mais próximo do real, podemos usar o arquivo JSON que criamos.

test/coffee_shop/integrations/coffee/response_test.exs
defmodule CoffeeShop.Integrations.Coffee.ResponseTest do
  use ExUnit.Case

  alias CoffeeShop.Integrations.Coffee.Response

  describe "build/1" do
    test "build from Tesla.Env structure" do
      response =
        File.read!("test/coffee_shop/integrations/coffee/fixtures/success.json")

      tesla_response = {:ok, %Tesla.Env{status: 200, body: response}}

      assert {:ok, %Response{body: body}} = Response.build(tesla_response)
      assert is_list(body)
    end
  end
end

Rodaremos o teste

mix test
 1) test build/1 build from Tesla.Env structure (CoffeeShop.Integrations.Coffee.ResponseTest)
     test/coffee_shop/integrations/coffee/response_test.exs:7
     Expected truthy, got false
     code: assert is_list(body)
     arguments:

         # 1
         "[{\"title\":\"Caramel Latte\",\"description\":\"Om du gillar latte med en speciell smak kan kar
amell latte vara det bästa alternativet för att ge dig en upplevelse av den naturliga sötman och krämighe
ten hos ångad mjölk och karamell.\",\"ingredients\":[\"Espresso\",\"Ångad mjölk\",\"Karamellsirap\"],\"im
age\":\"https://images.unsplash.com/photo-1599398054066-846f28917f38?auto=format&fit=crop&q=80&w=1887&ixl
ib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\"id\":3},{\"title\":\"Cappuccino
\",\"description\":\"Cappuccino är en latte som är gjord med mer skum än ångad mjölk, ofta med ett strö a
v kakaopulver eller kanel på toppen. Ibland kan du hitta variationer som använder grädde istället för mjö
lk eller sådana som tillsätter smakämnen också.\",\"ingredients\":[\"Espresso\",\"Ångad mjölk\",\"Foam\"]
,\"image\":\"https://images.unsplash.com/photo-1557006021-b85faa2bc5e2?auto=format&fit=crop&q=80&w=1887&i
xlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\"id\":4},{\"title\":\"American
o\",\"description\":\"Med en liknande smak som svart kaffe består americano av en espresso skott utspätt 
med hett vatten.\",\"ingredients\":[\"Espresso\",\"Hett vatten\"],\"image\":\"https://images.unsplash.com
/photo-1532004491497-ba35c367d634?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG
90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\"id\":5},{\"title\":\"Espresso\",\"description\":\"Ett espressoskot
t kan serveras ensamt eller användas som grund för de flesta kaffedrycker, som latte och macchiato.\",\"i
ngredients\":[\"Espresso\"],\"image\":\"https://images.unsplash.com/photo-1579992357154-faf4bde95b3d?auto
=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\
"id\":6},{\"title\":\"Macchiato\",\"description\":\"Macchiaton är en annan espresso-baserad dryck som har
 en liten mängd skum på toppen. Det är det glada mellanrummet mellan en cappuccino och en doppio.\",\"ing
redients\":[\"Espresso\",\"Foam\"],\"image\":\"https://images.unsplash.com/photo-1557772611-722dabe20327?
auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D
\",\"id\":7},{\"title\":\"Mocha\",\"description\":\"För alla chokladälskare där ute kommer ni att bli för
älskade i en mocha. Mocha är en choklad-espressodryck med ångad mjölk och skum.\",\"ingredients\":[\"Espr
esso\",\"Ångad mjölk\",\"Choklad\"],\"image\":\"https://images.unsplash.com/photo-1607260550778-aa9d29444
ce1?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3
D%3D\",\"id\":8},{\"title\":\"Hot Chocolate\",\"description\":\"Under kalla vinterdagar får en kopp varm 
choklad dig att känna dig bekväm och lycklig. Den får dig också att må bra eftersom den innehåller energi
givande koffein.\",\"ingredients\":[\"Choklad\",\"Mjölk\"],\"image\":\"https://images.unsplash.com/photo-
1542990253-0d0f5be5f0ed?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NDh8f
GhvdCUyMGNob2NvbGF0ZXxlbnwwfHwwfHx8MA%3D%3D\",\"id\":9},{\"title\":\"Chai Latte\",\"description\":\"Om du
 letar efter en smakfull varm dryck mitt i vintern, välj chai latte. Kombinationen av kardemumma och kane
l ger en underbar smak.\",\"ingredients\":[\"Te\",\"Mjölk\",\"Ingefära\",\"Kardemumma\",\"Kanel\"],\"imag
e\":\"https://images.unsplash.com/photo-1578899952107-9c390f1af1b7?w=900&auto=format&fit=crop&q=60&ixlib=
rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTJ8fGNoYWklMjBsYXR0ZXxlbnwwfHwwfHx8MA%3D%3D\",\"id\":10},{\"title\
":\"Matcha Latte\",\"description\":\"Matcha latte är en grön, hälsosam kaffedryck med finkrossad matcha-t
e och mjölk, erbjuder mild sötma, en unik smak och en mild koffeinkick.\",\"ingredients\":[\"Matcha-pulve
r\",\"Mjölk\",\"Socker*\"],\"image\":\"https://images.unsplash.com/photo-1536256263959-770b48d82b0a?w=900
&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8M3x8bWF0Y2hhJTIwbGF0dGV8ZW58MHx8MH
x8fDA%3D\",\"id\":11},{\"title\":\"Seasonal Brew\",\"description\":\"Säsongs kaffe med olika smaktoner so
m karamell, frukt och choklad\",\"ingredients\":[\"Kaffe\"],\"image\":\"https://images.unsplash.com/photo
-1611162458324-aae1eb4129a4?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8M
Tg1fHxib" <> ...

     stacktrace:
       test/coffee_shop/integrations/coffee/response_test.exs:14: (test)


Finished in 0.02 seconds (0.00s async, 0.02s sync)
1 test, 1 failure

Falhou como esperado, devido a não ser uma lista. Agora precisamos resolver isso na função Response.build/1.

test/coffee_shop/integrations/coffee/response_test.exs
defmodule CoffeeShop.Integrations.Coffee.Response do
  defstruct status: :integer, body: :map

  def build({:ok, %Tesla.Env{status: status, body: body}}) do
    response = %__MODULE__{
      status: status,
      body: JSON.decode!(body)
    }

    {:ok, response}
  end
end

Utilizamos o JSON.decode!/1 para decodificar o JSON de nossa resposta.

mix test test/coffee_shop/integrations/coffee/response_test.exs
.
Finished in 0.04 seconds (0.00s async, 0.04s sync)
1 test, 0 failures

Com o dado vindo no formato que o Elixir consegue tratar, podemos validar sua estrutura:

test/coffee_shop/integrations/coffee/response_test.exs
defmodule CoffeeShop.Integrations.Coffee.ResponseTest do
  use ExUnit.Case

  alias CoffeeShop.Integrations.Coffee.Response

  describe "build/1" do
    test "build from Tesla.Env structure" do
      response =
        File.read!("test/coffee_shop/integrations/coffee/fixtures/success.json")

      tesla_response = {:ok, %Tesla.Env{status: 200, body: response}}

      assert {:ok, %Response{body: body}} = Response.build(tesla_response)
      assert is_list(body)

      assert %{
        "title" => _,
        "description" => _,
        "id" => _,
        "image" => _
       } = List.first(body)
    end
  end
end
mix test test/coffee_shop/integrations/coffee/response_test.exs
.
Finished in 0.04 seconds (0.00s async, 0.04s sync)
1 test, 0 failures

Nosso response está funcionando e pronto para uso. Vamos ver se tudo continua como deve estar.

mix test
> mix test
.....
Finished in 0.6 seconds (0.00s async, 0.6s sync)
1 doctest, 4 tests, 0 failures

Tudo em seu devido lugar.

Atualizado