> For the complete documentation index, see [llms.txt](https://consumindo-apis-com-elixir.cafecomelixir.com.br/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://consumindo-apis-com-elixir.cafecomelixir.com.br/construindo-um-cliente-usando-tesla/mockando-requisicoes-do-cliente-com-bypass.md).

# 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.
