Conseguimos nos conectar ao serviço e obtemos a resposta de nosso pedido.
Requisitamos e essa estrutura foi apresentada.
{:ok, %Tesla.Env{ method: :get, url: "https://api.sampleapis.com/coffee/hot", query: [], headers: [ {"connection","keep-alive"}, {"date","Thu, 11 Apr 2024 18:41:16 GMT"}, {"etag","W/\"21df-Qjes7uaeQItDszmliRPvMUXoGIs\""}, {"server","cloudflare"}, {"content-length","8671"}, {"content-type","application/json; charset=utf-8"}, {"x-powered-by","Express"}, {"access-control-allow-origin","*"}, {"x-ratelimit-limit","5000"}, {"x-ratelimit-remaining","4999"}, {"x-ratelimit-reset","1712861765"}, {"x-content-type-options","nosniff"}, {"cf-cache-status","DYNAMIC"}, {"report-to","{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=t8jefu1i1%2FKUiH7aYPEOaufCEApMVHn4aHootP6XuJhDF0L5XC6UmY33iprTNR2IXOnbBsNgN7E1a%2Fsuc5rZ9Pm9Qu%2Fp3gtobw4sxZmvrxTR8NXYOdajBzbwmJtJAw%2FG5eu1Pcg%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}"}, {"nel","{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}"}, {"cf-ray","872d1c1dce920323-GRU"}, {"alt-svc","h3=\":443\"; ma=86400"} ], body: "[{\"title\":\"Black Coffee\",\"description\":\"Svart kaffe är så enkelt som det kan bli med malda kaffebönor dränkta i hett vatten, serverat varmt. Och om du vill låta fancy kan du kalla svart kaffe med sitt rätta namn: café noir.\",\"ingredients\":[\"Coffee\"],\"image\":\"https://images.unsplash.com/photo-1494314671902-399b18174975?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\"id\":1},{\"title\":\"Latte\",\"description\":\"Som den mest populära kaffedrycken där ute består latte av en skvätt espresso och ångad mjölk med bara en gnutta skum. Den kan beställas utan smak eller med smak av allt från vanilj till pumpa kryddor.\",\"ingredients\":[\"Espresso\",\"Ångad mjölk\"],\"image\":\"https://images.unsplash.com/photo-1561882468-9110e03e0f78?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTl8fGxhdHRlfGVufDB8fDB8fHww\",\"id\":2},{\"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.u" <> ...,
status: 200, opts: [], __module__: CoffeeShop.Integrations.Coffee.Client, __client__: %Tesla.Client{fun: nil, pre: [], post: [], adapter: nil} }}
Essa é a resposta que o Tesla nos trás.
A primeira coisa que gosto de fazer, é tirar o Tesla de vista. Gosto de criar um estrutura para recebermos a resposta e poder trafegar pelo nosso contexto sem a dependência do Tesla se espalhando.
Para isso criaremos uma estrutura simples com dois atributos
status -> responsável por guardaro status da resposta
body -> responsável por guardar os dados da requisição
Vamos criar um novo modulo chamada response dentro do contexto coffee
Eu gosto disso para facilitar o entendimento e deixar as coisas mais homogeneas.
Para utilizar essa estrutura, vamos criar uma função chamada build/1 nesse mesmo arquivo Response e lidar com a resposta do Tesla, criando nosso ponto de transição. Para pegar os dados que queremos, vamos utilizar pattern matching e extrair as informações ao mesmo tempo que garantimos que nossa resposta virá com a estrutura esperada. Em seguida, iremos utilizar nossa estrutura de Response para devolver os dados extraídos.
lib/integrations/coffee/response.ex
defmoduleCoffeeShop.Integrations.Coffee.Responsedodefstruct status: :integer, body: :mapdefbuild({:ok, %Tesla.Env{status: status, body: body}}) do response = %__MODULE__{ status: status, body: body } {:ok, response}endend
Adicionamos o :ok, como parte da convenção do elixir, você pode ver mais sobre nesse link.
Elegante não? Que acha de um teste para isso?
test/integrations/coffee/response_test.exs
defmoduleCoffeeShop.Integrations.Coffee.ResponseTestdouseExUnit.CasealiasCoffeeShop.Integrations.Coffee.Response describe "build/1"do test "build from Tesla.Env structure"do tesla_response = {:ok, %Tesla.Env{status: 200, body: "something here"}} assert {:ok, %Response{}} =Response.build(tesla_response)endendend
Rodaremos o teste para ver ele passando
mixtest
> mix test test/integrations/coffee/response_test.exs.Finishedin0.02seconds (0.00s async,0.02ssync)1test,0failures
Temos agora um forma estruturada de resposta. Precisamos alterar nosso cliente, esperamos que ele responda nossa nova estrutura. Vamos no teste primeiro. Nosso teste está dizendo que a resposta esperada é um Tesla.Env, mas agora possuímos nosso própria estrutura. Vamos trocar para o Response.
Também podemos fazer a validação do status. Nesse caso receberemos um status 200
test/integrations/coffee/client_test.exs
defmoduleCoffeeShop.Integrations.Coffee.ClientTestdouseExUnit.CasealiasCoffeeShop.Integrations.Coffee.ClientaliasCoffeeShop.Integrations.Coffee.Response describe "all_hot_coffees/0"do test "respond a list of hot coffees"do assert {:ok, %Response{status: status}} =Client.all_hot_coffees() assert status ==200endendend
Algo digno de se notar, acabamos com a dependência do Tesla em nosso teste. Não somos mais dependentes dele, pelo menos aqui no teste e isso é incrível.
Mas se você rodar esse teste, ele vai quebrar.
mix test
> mix test test/integrations/coffee/client_test.exs1) test all_hot_coffees/0 respond a list of hot coffees (CoffeeShop.Integrations.Coffee.ClientTest) test/integrations/coffee/client_test.exs:13match (=) failed code: assert {:ok, %Response{}} =Client.all_hot_coffees() left: {:ok, %CoffeeShop.Integrations.Coffee.Response{}} right: { :ok, %Tesla.Env{ __client__: %Tesla.Client{fun: nil, pre: [], post: [], adapter: nil}, __module__: CoffeeShop.Integrations.Coffee.Client, body: "[{\"title\":\"Black Coffee\",\"description\":\"Svart kaffe är så enkelt som det kan bli med malda kaffebönor dränkta i hett vatten, serverat varmt. Och om du vill låta fancy kan du kalla svart kaffe med sitt rätta namn: café noir.\",\"ingredients\":[\"Coffee\"],\"image\":\"https://images.unsplash.com/photo-1494314671902-399b18174975?auto=format&fit=crop&q=80&w=1887&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\"id\":1},{\"title\":\"Latte\",\"description\":\"Som den mest populära kaffedrycken där ute består latte av en skvätt espresso och ångad mjölk med bara en gnutta skum. Den kan beställas utan smak eller med smak av allt från vanilj till pumpa kryddor.\",\"ingredients\":[\"Espresso\",\"Ångad mjölk\"],\"image\":\"https://images.unsplash.com/photo-1561882468-9110e03e0f78?auto=format&fit=crop&q=60&w=800&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTl8fGxhdHRlfGVufDB8fDB8fHww\",\"id\":2},{\"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.u" <> ...,
headers: [{"connection", "keep-alive"}, {"date", "Thu, 11 Apr 2024 19:37:53 GMT"}, {"etag", "W/\"21df-Qjes7uaeQItDszmliRPvMUXoGIs\""}, {"server", "cloudflare"}, {"content-length", "8671"}, {"content-type", "application/json; charset=utf-8"}, {"x-powered-by", "Express"}, {"access-control-allow-origin", "*"}, {"x-ratelimit-limit", "5000"}, {"x-ratelimit-remaining", "4991"}, {"x-ratelimit-reset", "1712864465"}, {"x-content-type-options", "nosniff"}, {"cf-cache-status", "DYNAMIC"}, {"report-to", "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=7uthBMHN%2FyIQOeGjes6brn7pVz5jkwb6u2f8wsWg5kqNYpwpgL4PmuMSqoptF3GfAcomcro%2BF1Xgb%2BgO7XyYtlwbnqvluFhvMqmfDXyVnzwo0IDw%2Fow23Ab%2B1ID8zcNWqb6Njpo%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}"}, {"nel", "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}"}, {"cf-ray", "872d6f0b1f2da415-GRU"}, {"alt-svc", "h3=\":443\"; ma=86400"}],
method: :get, opts: [], query: [], status: 200, url: "https://api.sampleapis.com/coffee/hot" } } stacktrace: test/integrations/coffee/client_test.exs:14: (test)Finishedin0.7seconds (0.00s async,0.7s sync)1 test,1 failure
O problema é, estamos esperando um %Response{}, mas nosso cliente retornou um %Tesla.Env{}. Precisamos alterar nossa implementação em client.ex. Temos essa implementação:
O importante está na linha 7. A função get/1 é do Tesla e sua resposta é um Tesla.Env. Estando na ultima linha da função é o que será retornado. Precisamos utilizar aqui nosso Response.build/1 para retonrnar a estrutura esperada. Vamos fazer essa atualização e aproveitar para deixar as coisas mais bonitas usando pipe.
Adicionamos nosso Response.build/1 que espera um {:ok, %Tesla.Env{}} e tratamos a resposta para retornar um %Response{} tirando a dependência do Tesla, colocamos a responsabilidade para algo em nosso controle.
Conseguimos criar uma estrutura de resposta em nosso controle tirando a responsabilidade de uma biblioteca de terceiro. Isso pode parecer um tanto simples e exagerado, mas vai ser de grande ajuda. Tanto para debug quanto para leitura e entendimento.
Agora precisamos falar sobre estrategia de testes. Vamos lá.