# Mockando requisições do cliente com Bypass

Primeiro vamos criar o teste que precisamos para quando o serviço está quebrado. Um erro 500 critico. Vamos adicionar o  novo teste em nosso arquivo `test/integrations/coffee/client_test.exs`&#x20;

{% code title="test/integrations/coffee/client\_test.exs" lineNumbers="true" %}

```elixir
defmodule CoffeeShop.Integrations.Coffee.ClientTest do
  use ExUnit.Case

  alias CoffeeShop.Integrations.Coffee.Client

  describe "all_hot_coffees/0" do
    # ...
    
    test "service is crashed" do
      assert {:ok, %Response{status: status}} = Client.all_hot_coffees()
      assert status == 500
    end
  end
end
```

{% endcode %}

Em nosso teste queremos que o status seja 500. Mas ainda estamos batendo no serviço real. Precisamos configurar o bypass e utilizar em nosso teste, para conseguir simular as respostas que queremos.

<pre class="language-elixir" data-title="test/integrations/coffee/client_test.exs" data-line-numbers><code class="lang-elixir">defmodule CoffeeShop.Integrations.Coffee.ClientTest do
  use ExUnit.Case

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

  describe "all_hot_coffees/0" do
<strong>    setup do
</strong><strong>      bypass = Bypass.open(port: 3000)
</strong><strong>      {:ok, bypass: bypass}
</strong><strong>    end
</strong>    
    # ...

<strong>    test "service is crashed", %{bypass: bypass} do
</strong>      assert {:ok, %Response{status: status}} = Client.all_hot_coffees()
      assert status == 500
    end
  end
end

</code></pre>

Adicionamos na função setup o `Bypass.open/1`, que tem como objetivo abrir um serviço falso. Esse serviço é levantado em `http//localhost` podendo ser adicionado a porta em nosso caso, escolhemos a porta 3000. Isso pode conflitar com outros serviços em seu computador, então, caso de problema, troque a porta.

Com o serviço de pé, precisamos ajeitar nosso mock. Para criar o mock, precisamos primeiro identificar qual a requisição queremos mockar. Para isso o *Bypass* possui o método `expect_once` e `expect` . Um espera apenas uma chamada, o outro uma ou mais.&#x20;

<pre class="language-elixir" data-title="test/integrations/coffee/client_test.exs" data-line-numbers><code class="lang-elixir">defmodule CoffeeShop.Integrations.Coffee.ClientTest do
  use ExUnit.Case

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

  describe "all_hot_coffees/0" do
    setup do
      bypass = Bypass.open()
      {:ok, bypass: bypass}
    end

    test "respond a list of hot coffees", %{bypass: bypass} do
      assert {:ok, %Response{status: status}} = Client.all_hot_coffees()
      assert status == 200
    end

    test "service is crashed", %{bypass: bypass} do
<strong>      response = "Server exploded"
</strong>      
<strong>      Bypass.expect_once(bypass, "GET", "/coffee/hot", fn conn ->
</strong><strong>        Plug.Conn.resp(conn, 500, response)
</strong><strong>      end)
</strong>
      assert {:ok, %Response{status: status}} = Client.all_hot_coffees()
      assert status == 500
    end
  end
end

</code></pre>

Chamamos a função `Bypass.expect_once/4`, passando a instância do serviço, o verbo que utilizamos, o recurso e a função de *callback*, que nada mais é que a resposta da requisição. *Plug* montara uma resposta HTTP para nós. É aqui que a informação falsa deve aparecer. Feito isso, vamos rodar nosso teste

```
mix test
```

```sh
> mix test 
.

  1) test all_hot_coffees/0 service is crashed (CoffeeShop.Integrations.Coffee.ClientTest)
     test/integrations/coffee/client_test.exs:18
     Assertion with == failed
     code:  assert status == 500
     left:  200
     right: 500
     stacktrace:
       test/integrations/coffee/client_test.exs:26: (test)

...
Finished in 0.8 seconds (0.00s async, 0.8s sync)
1 doctest, 4 tests, 1 failure
```

O teste falhou. Ainda temos um status 200. Isso quer dizer que estamos batendo no serviço real. Com toda certeza, ainda estamos indo para a URL base aplicado pelo plug e o novo serviço mora em `http://localhost` e precisamos bater la para fins de teste.

Com isso, temos que achar uma forma de poder reconfigurar a URL do nosso cliente quando estamos no teste. Existe a possiblidade de configurar pelas variáveis de ambiente, adicionando um para `config/config.exs` e um para `config/test.exs`. Porém, sou bem visual e usar isso:

```elixir
plug(Tesla.Middleware.BaseUrl, Application.get_env(:coffee_shope, :coffee)[:base_url])
```

Ao invés disso:

```elixir
plug(Tesla.Middleware.BaseUrl, "https://api.sampleapis.com")
```

Me da dor de cabeça.

A opção que gosto é configurar com base do parâmetro `opts` passando para o cliente.&#x20;

```elixir
Client.all_hot_coffees(base_url: "http://localhost")
```

Não só a *url* *base* mas todo tipo de configuração gosto de passar pelos parâmetros. Isso facilita a configuração e a utilização para os testes.

O problema agora é que utilizamos o *plug* para configurar e não conseguimos alcançar ele dessa forma. Precisamos mudar a forma como nosso cliente esta rodando.&#x20;

* Remover plug de configuração
* Passar *url* base para função

Vamos começar removendo o plug para evitar  as configurações via macro.

{% code title="lib/integrations/coffee/client.ex" lineNumbers="true" %}

```elixir
defmodule CoffeeShop.Integrations.Coffee.Client do
  use Tesla

  alias CoffeeShop.Integrations.Coffee.Response

  def all_hot_coffees do
    "/coffee/hot"
    |> get()
    |> Response.build()
  end
end

```

{% endcode %}

Podemos rodar os testes e ver o tamanho do estrago.

```
mix test
```

```
mix test 
Compiling 1 file (.ex)


  1) test all_hot_coffees/0 respond a list of hot coffees (CoffeeShop.Integrations.Coffee.ClientTest)
     test/integrations/coffee/client_test.exs:13
     ** (FunctionClauseError) no function clause matching in CoffeeShop.Integrations.Coffee.Response.build/1

     The following arguments were given to CoffeeShop.Integrations.Coffee.Response.build/1:
     
         # 1
         {:error, {:no_scheme}}
     
     Attempted function clauses (showing 1 out of 1):
     
         def build({:ok, %Tesla.Env{status: status, body: body}})
     
     code: assert {:ok, %Response{status: status}} = Client.all_hot_coffees()
     stacktrace:
       (coffee_shop 0.1.0) lib/integrations/coffee/response.ex:4: CoffeeShop.Integrations.Coffee.Response.build/1
       test/integrations/coffee/client_test.exs:14: (test)



  2) test all_hot_coffees/0 service is crashed (CoffeeShop.Integrations.Coffee.ClientTest)
     test/integrations/coffee/client_test.exs:18
     ** (FunctionClauseError) no function clause matching in CoffeeShop.Integrations.Coffee.Response.build/1

     The following arguments were given to CoffeeShop.Integrations.Coffee.Response.build/1:
     
         # 1
         {:error, {:no_scheme}}
     
     Attempted function clauses (showing 1 out of 1):
     
         def build({:ok, %Tesla.Env{status: status, body: body}})
                                                                                                                                                                                                                                                                                                                                                     M:   CPU: 
     code: assert {:ok, %Response{status: status}} = Client.all_hot_coffees()
     stacktrace:
       (coffee_shop 0.1.0) lib/integrations/coffee/response.ex:4: CoffeeShop.Integrations.Coffee.Response.build/1
       test/integrations/coffee/client_test.exs:25: (test)

...
Finished in 0.1 seconds (0.00s async, 0.1s sync)
1 doctest, 4 tests, 2 failures
```

A vantagem de ter testes, é que saberemos se algo da errado em nossa mudança de forma prática e rápida.  Caso quebre, basta resolver, ficando verde, temos um indicativo que tudo voltou a funcionar. Vamos fazer essas belezinhas passarem.

O problema aqui é não ter configurado a *url base* do cliente, uma vez que removemos o plug. Precisamos ir agora para a segunda parte, montar nossa função de criação do cliente.

* ~~Remover plug de configuração~~
* Passar *url* base para função

Precisamos passar um parâmetro de configuração para dentro de nossa função de requisição `all_hot_coffees/1`. Utilizaremos uma lista para facilitar a adição de novos argumentos.  Utilizaremos `Keyword.get/3` para obter o dado da lista:

**Keyword.get/3**

1. **Primeiro argumento**: Lista
2. **Segundo argumento:** chave da lista
3. **Terceiro argumento:** Valor padrão caso não encontre a chave

Funcionará assim:

<pre class="language-elixir" data-line-numbers><code class="lang-elixir">defmodule CoffeeShop.Integrations.Coffee.Client do
  use Tesla

  alias CoffeeShop.Integrations.Coffee.Response

<strong>  def all_hot_coffees(opts \\ []) do
</strong><strong>    base_url = Keyword.get(opts, :base_url, "https://api.sampleapis.com")
</strong>
<strong>    "#{base_url}/coffee/hot"
</strong>    |> get()
    |> Response.build()
  end
end

</code></pre>

Passamos o parâmetros de `opts` para nossa função e dissemos que caso não encontre em `opts` a chave `base_url`, utilizar o valor `https://api.sampleapis.com`. Nossa implementação continuará funcionando com a url base padrão, mas temos a possibilidade de alterar caso necessario, em nosso atual cenário, quando precisar bater no serviço de teste.&#x20;

Mas isso não é tudo que precisamos fazer. Se rodar o teste estaremos ainda com o problema de sempre acessar o serviço real. Para resolver isso, precisamos passar `base_url` nos parâmetros da chamada em nosso teste como esperado.

{% code title="test/integrations/coffee/client\_test.exs" lineNumbers="true" %}

```elixir
defmodule CoffeeShop.Integrations.Coffee.ClientTest do
  use ExUnit.Case

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

  describe "all_hot_coffees/0" do
    setup do
      bypass = Bypass.open(port: 3000)
      {:ok, bypass: bypass}
    end

    test "respond a list of hot coffees" do
      assert {:ok, %Response{status: status}} = Client.all_hot_coffees()
      assert status == 200
    end

    test "service is crashed", %{bypass: bypass} do
      response = "Server explode"

      Bypass.expect_once(bypass, "GET", "/coffee/hot", fn conn ->
        Plug.Conn.resp(conn, 500, response)
      end)
      
      opts = [
        base_url: "http://localhost:3000"
      ]

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

      assert status == 500
    end
  end
end

```

{% endcode %}

Rodaresmos o teste

```
mix test
```

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

Randomized with seed 431636
```

Nada mal em? Criamos um mecanismo simples de configuração de nosso cliente. Podendo adicionar mais opções, que vamos explorar mais a frente. Por hora, conseguimos criar chamadas falsas do jeito que quisermos.

Tem duas coisas me incomodando:

1. Estamos usando uma macro do Tesla que não tras muito ganho para nós.
2. Ainda estamos batendo no serviço real em nosso primeiro teste, é interessante não fazer isso

Vamos começar removendo a macro. A macro do Tesla nos da poder de usar algumas funções sem precisar utilizar o prefixo, como se pertencesse a esse modulo. Mas além de criar um acoplamento forte também não fica muito legal visualmente.&#x20;

<pre class="language-elixir"><code class="lang-elixir">"#{base_url}/coffee/hot"
<strong>|> get()
</strong>|> Response.build()
</code></pre>

Podemos chamar a mesma função usando `Tesla.get/1`, não precisando da macro.

{% code title="lib/integrations/coffee/client.ex" lineNumbers="true" %}

```elixir
defmodule CoffeeShop.Integrations.Coffee.Client do
  alias CoffeeShop.Integrations.Coffee.Response

  def all_hot_coffees(opts \\ []) do
    base_url = Keyword.get(opts, :base_url, "https://api.sampleapis.com")

    "#{base_url}/coffee/hot"
    |> Tesla.get()
    |> Response.build()
  end
end

```

{% endcode %}

Sem mais macros. Sem mais magia obscura.&#x20;

1. ~~Estamos usando uma macro do Tesla que não trás muito ganho para nós.~~
2. Ainda estamos batendo no serviço real em nosso primeiro teste, é interessante não fazer isso

Vamos atualizar nosso teste para que use o dado mockado e pare de realizar a requisição para o serviço real.

<pre class="language-elixir" data-title="test/integrations/coffee/client_test.exs" data-line-numbers><code class="lang-elixir">defmodule CoffeeShop.Integrations.Coffee.ClientTest do
  use ExUnit.Case

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

  describe "all_hot_coffees/0" do
    setup do
      bypass = Bypass.open(port: 3000)
      {:ok, bypass: bypass}
    end

    test "respond a list of hot coffees", %{bypass: bypass} do
<strong>      response = ""
</strong>
<strong>      Bypass.expect_once(bypass, "GET", "/coffee/hot", fn conn ->
</strong><strong>        Plug.Conn.resp(conn, 200, response)
</strong><strong>      end)
</strong>
<strong>      opts = [
</strong><strong>        base_url: "http://localhost:3000"
</strong><strong>      ]
</strong>
<strong>      assert {:ok, %Response{status: status}} = Client.all_hot_coffees(opts)
</strong>      assert status == 200
    end

    # ...
  end
end

</code></pre>

Uma vez o mecanismo pronto, basta apenas definir o *mock* e configurar a *url* base  para nosso serviço falso.

* ~~Estamos usando uma macro do Tesla que não trás muito ganho para nós.~~
* ~~Ainda estamos batendo no serviço real em nosso primeiro teste, é interessante não fazer isso~~

Estamos 100% independentes agora. Não irão mais nos cobrar por requisições de teste. Também conseguimos limpar nosso cliente e remover todas os acoplamentos e dependências que não queremos.

Vamos mais a frente, temos muito chão ainda.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://consumindo-apis-com-elixir.cafecomelixir.com.br/construindo-um-cliente-usando-tesla/mockando-requisicoes-do-cliente-com-bypass.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
