💧🍔 Projeto Rockelivery: API para Pedidos em um Restaurante com Elixir e Phoenix (Parte 4)
Tags: elixir, phoenix, ecto, braziliandevs
Published: 2021-06-15 00:27:54

Essa é a quarta parte do projeto __Rockelivery__. Esse projeto faz parte do Bootcamp da [Rocketseat](https://rocketseat.com.br/), ministrado pelo professor [Rafael Camarda](https://www.instagram.com/rafacamarda/).

## Conteúdo:
* [🙋‍♂️ Introdução](#introducao)
* [🕺 Testando o User Schema](#testando-o-user-schema)
 - [Testando a função changeset/2](#testando-a-funcao-changeset)
  - [Testando a função changeset/2 - Primeiro Cenário](#testando-a-funcao-changeset-primeiro-cenario)
  - [Testando a função changeset/2 - Segundo Cenário](#testando-a-funcao-changeset-segundo-cenario)
  - [Testando a função changeset/2 - Terceiro Cenário](#testando-a-funcao-changeset-terceiro-cenario)
* [🤖 Refatorando com ExMachina](#refatorando-com-exmachina)
* [➕ Testando o módulo Users.Create](#testando-o-modulo-users-create)
* [🏭 Refatorando a Factory](#refatorando-a-factory)
* [🕹️ Testando o UsersController](#testando-o-users-controller)
 - [Testando a função create/2 - Primeiro Cenário](#testando-a-funcao-call-primeiro-cenario)
  - [Testando a função create/2 - Segundo Cenário](#testando-a-funcao-call-segundo-cenario)
  - [Testando a função delete/2](#testando-a-funcao-delete)
* [😎 Testando a UsersView](#testando-a-users-view)
* [👀 Verificando a Cobertura de Testes](#verificando-a-cobertura-de-testes)

[![A Jornada do Autodidata em Inglês](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkoqkmh9hic17tnddjf6.png)](https://go.hotmart.com/R93865620U)

## 🙋‍♂️ Introdução <a name="introducao"></a>

Nessa __Parte 4__ do projeto vamos abordar o assunto de testes. Aconselho você a instalar uma biblioteca externa chamada __ExCoveralls__ para verificar melhor a cobertura dos testes. Você também pode instalar a lib __ExMachina__ para facilitar o setup dos testes. A instalação dessas bibliotecas você pode encontrar aqui: [Instalação das ferramentas de uma pessoa desenvolvedora Elixir](https://dev.to/maiquitome/instalacao-das-ferramentas-de-um-desenvolvedor-elixir-1bjm#excoveralls)

Somente as funções públicas *(def)* serão testadas.

Quando começar a fazer um teste, deixe sempre falhar primeiro.

Usaremos o seguinte código nos arquivos de teste:
```elixir
use Rockelivery.DataCase, async: true
```
Ele servirá para rodar os testes em paralelo. Isto é recomendado apenas para o banco de dados PostgreSQL. Se abrirmos o arquivo deste módulo `Rockelivery.DataCase` podemos ver essas informações:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1nikb66mvaiq2qvuvk0m.png) 
Com o `Rockelivery.DataCase` também teremos a tradução dos erros do changeset:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/foqc64xn0hgy1752f93u.png)

## 🕺 Testando o User Schema <a name="testando-o-user-schema"></a>

Vamos testar o arquivo: `lib/rockelivery/user.ex`.

Crie o arquivo de teste em `test/rockelivery/user_test.exs`.

### Testando a função changeset/2 <a name="testando-a-funcao-changeset"></a>

Neste teste teremos 3 cenários:

```elixir
defmodule Rockelivery.UserTest do
  use Rockelivery.DataCase, async: true

  # Vamos testar aqui a função changeset com aridade 2
  describe "changeset/2" do
    # Primerio Cenário
    test "when all params are valid, returns a valid changeset" do
    end

    # Segundo Cenário
    test "when updating a changeset, returns a valid changeset with the given changes" do
    end

    # Terceiro Cenário
    test "when there are some error, returns an invalid changeset" do
    end
  end
end
```

#### Primeiro Cenário <a name="testando-a-funcao-changeset-primeiro-cenario"></a>

Vamos para o primeiro cenário (quando todos os parâmetros são válidos retorna um changeset válido). Ao fazermos um teste, procuramos na maioria das vezes comparar dois lados. Se tentarmos comparar a __resposta__ com a __resposta esperada__, ao rodarmos o comando `mix test`, podemos ver que o teste falha como queríamos, e que a __resposta__ é mesmo um __changeset__:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rkl0bfrchj7lo4cla9o7.png)![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ravhfwbhxrvfpf5pxjrq.png) 
 
Então, neste caso não faremos uma igualdade (==). Para este caso, a melhor forma de fazermos o teste é realizando __Pattern Matching (=)__. 

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gvj0j0j5vp7p9s6gtms7.png) 

Agora, ao rodarmos o comando `mix test`, podemos ver que o teste falha novamente como esperado, mas que os dois lados agora são __changesets__, porém com dados diferentes em cada __changeset__:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4spiicx81bnsaay02a0s.png)
 
Vamos agora fazer o teste passar, colocando os dados iguais nos dois lados:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnfgpp22nnl9rsezois7.png)

Agora, ao rodarmos o comando `mix test`, podemos ver que o teste passa com sucesso!

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cudzduibiepxqma99vih.png)

Então, por enquanto o código do arquivo `test/rockelivery/user_test.exs` fica assim:

```elixir
defmodule Rockelivery.UserTest do
  use Rockelivery.DataCase, async: true

  alias Ecto.Changeset
  alias Rockelivery.User

  describe "changeset/2" do
    # Primerio Cenário
    test "when all params are valid, returns a valid changeset" do
      params = %{
        age: 27,
        address: "Rua do Maiqui",
        cep: "12345678",
        cpf: "12345678901",
        email: "maiqui@tome.com.br",
        password: "123456",
        name: "Maiqui Tomé"
      }

      response = User.changeset(params)

      assert %Changeset{changes: %{name: "Maiqui Tomé"}, valid?: true} = response
    end

    # Segundo Cenário
    test "when updating a changeset, returns a valid changeset with the given changes" do
    end

    # Terceiro Cenário
    test "when there are some error, returns an invalid changeset" do
    end
  end
end
```
 
#### Segundo Cenário <a name="testando-a-funcao-changeset-segundo-cenario"></a>

Vamos agora para o segundo cenário (quando atualiza um changeset, retorna um changeset válido com os dados passados). Vamos começar deixando o teste falhar, colocando nomes diferentes:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z6chco5dphei802i560w.png) 

Como esperado o teste falha:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbqnjwajuzzdozcp1ee6.png)
 
Vamos colocar os nomes iguais pra ver o teste passar:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zn10a0r3hra6144k5y11.png)

Agora o segundo cenário está finalizado:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myss0pj1rishul4zox6x.png)  

#### Terceiro Cenário <a name="testando-a-funcao-changeset-terceiro-cenario"></a>

Vamos agora para o terceiro cenário. Vamos testar agora as validações. Dessa vez vamos usar a variável `expected_response` pois usaremos a função `Rockelivery.DataCase.errors_on(changeset)` que comentamos na introdução.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oio4y2ye0xyq12i0bjpp.png)

Agora vamos ver o teste falhar:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v6zwojqv54bmpjgmggrt.png)

Podemos perceber que não apareceu a mensagem dizendo que o password deve ter pelo menos 6 caracteres. O que aconteceu? vamos ver o schema do usuário:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6is2z42wknx4cynw02i2.png)

Isso foi o nosso teste encontrando um bug que havíamos deixado :)

Após alterar o nome do campo de `password_hash` para `password` vamos executar novamente o comando `mix test`:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/83fqv0ohwd076wkh3avc.png)
  
Vamos agora alterar o nosso teste para ele passar:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2h418yvjh656ffqxl6f3.png)

Terceiro teste finalizado com sucesso!
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4o2ft3ik91i2i1n9lhhr.png)  

## 🤖 Refatorando com ExMachina <a name="refatorando-com-exmachina"></a>

Crie o arquivo `test/support/factory.ex`:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4v6zvs1qpno3b3kaf5wi.png)

O seu conteúdo deve ser semelhante:  
```elixir
defmodule Rockelivery.Factory do
  use ExMachina

  def user_params_factory do
    %{
      age: 27,
      address: "Rua do Maiqui",
      cep: "12345678",
      cpf: "12345678901",
      email: "maiqui@tome.com.br",
      password: "123456",
      name: "Maiqui Tomé"
    }
  end
end
```

Em `test/rockelivery/user_test.exs`:

Importe o módulo `Rockelivery.Factory`:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g333ag09opumtof44kxo.png) 

Primeiro Cenário:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/onij0mjd1hzgvkicxhkm.png) 

Segundo Cenário:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nt9934jcthu3olpo9z94.png) 

Terceiro Cenário:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/od8afbsxwaxln6m4k049.png) 

Executamos o comando `mix test` e tudo continua funcionando como antes:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o2c72vl7zpu29t6nu3i6.png)
 
## ➕ Testando o módulo Users.Create <a name="testando-o-modulo-users-create"></a>

Vamos testar o módulo `lib/rockelivery/users/create.ex`.

Crie o arquivo de teste em `test/rockelivery/users/create_test.exs`.

Nos testes desse módulo não vamos entrar em detalhes, pois os testes vão ser parecidos. Tente você fazer sozinho e depois olhe a resposta. Deixe sempre o teste falhar por primeiro.

O seu código deve ficar semelhante a esse:

```elixir
defmodule Rockelivery.Users.CreateTest do
  use Rockelivery.DataCase, async: true

  import Rockelivery.Factory

  alias Rockelivery.{Error, User}
  alias Rockelivery.Users.Create

  describe "call/1" do
    test "when all params are valid, returns the user" do
      params = build(:user_params)

      response = Create.call(params)

      assert {:ok, %User{id: _id, age: 27, email: "maiqui@tome.com.br"}} = response
    end

    test "when there are invalid params, returns an error" do
      params = build(:user_params, %{age: 15, password: "123"})

      response = Create.call(params)

      expected_response = %{
        age: ["must be greater than or equal to 18"],
        password: ["should be at least 6 character(s)"]
      }

      assert {:error, %Error{result: changeset, status: :bad_request}} = response

      assert errors_on(changeset) == expected_response
    end
  end
end
```

## 🏭 Refatorando a Factory <a name="refatorando-a-factory"></a>

Como as chaves dos parâmetros na nossa conexão vem em formato de `string`, vamos precisar refatorar a nossa factory para podermos usar ela nos testes do `controller`.

Em `test/support/factory.ex`:
 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ymncnvo2j7hm7m7rj8m.png)

Após essa alteração, ao executarmos o comando `mix test` percebermos que precisamos refatorar alguns testes:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pnsjnikchhxkzkqvcsyd.png)
 
Vamos refatorar em `test/rockelivery/users/create_test.exs`:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/825eocpbllamoprr6zms.png)

Vamos para o segundo teste que precisa ser refatorado:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kobe5qchx2muevs0iniv.png)

Em `test/rockelivery/user_test.exs`:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qy7mhu19sijl3hijvxy4.png) 

Agora ao executarmos o comando `mix test`:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ibh1wkibinbss0ucdrpr.png)

## 🕹️ Testando o UsersController <a name="testando-o-users=controller"></a>

Vamos testar o módulo `lib/rockelivery_web/controllers/users_controller.ex`.

Crie o arquivo de teste em `test/rockelivery_web/controllers/users_controller_test.exs`.

No teste do `controller` usaremos `RockeliveryWeb.ConnCase` ao invés de `Rockelivery.DataCase`, que tem as mesmas funcionalidades mas com facilidades para testarmos um `controller`.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/abnlgbj6sca2s818y96c.png)

### Testando a função create/2 - Primeiro Cenário <a name="testando-a-funcao-call-primeiro-cenario"></a>

Vamos para o primeiro cenário: quando todos os parâmetros forem válidos, cria o usuário.

Vamos codar e deixar o teste falhar primeiro:
```elixir
defmodule RockeliveryWeb.UsersControllerTest do
  use RockeliveryWeb.ConnCase, async: true

  import Rockelivery.Factory

  describe "create/2" do
    test "when all params are valid, creates the user", %{conn: conn} do
      params = build(:user_params)

      response =
        conn
        |> post("/api/users", params)
        |> json_response(:ok)

      assert "teste" = response
    end
  end
end
```

Vamos entender esse código:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qka1w0okosp2resdirih.png) 

* Como a rota é de criação então usamos a função `Phoenix.ConnTest.post/3` confira na documentação: https://hexdocs.pm/phoenix/Phoenix.ConnTest.html#post/3
 - Existem também as funções `get`, `delete`, `put`...

* `Phoenix.ConnTest.json_response/2` afirma o código de status fornecido, que temos uma resposta json e retorna a resposta JSON decodificada se foi definida ou enviada. Veja mais na documentação: https://hexdocs.pm/phoenix/Phoenix.ConnTest.html#json_response/2

Agora vamos executar o comando `mix test` e ver o teste falhar:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwo1uslq253s3h17m1rq.png) 

Os nossos dados não foram decodificados para JSON pois houve um erro de status, ele esperava 201 (:created) e recebeu 200 (:ok). Então, vamos arrumar isso e vamos executar novamente `mix test`:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfe36pzpxtx7epuf4g8l.png) 

Copie o map no terminal e cole no lugar de "teste", ao salvar, a extensão do VSCODE ElixirLS irá formatar o código para nós. Como o `id` muda, então colocamos um `_id` ignorando o valor.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aw5f02jrvjzp4fd9p5mx.png)

Ao executarmos `mix test` verificamos que os testes estão passando com sucesso:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ea22i6bxjrpueimkctuz.png)

#### Refatorando

Altere o seu código usando a função `Routes.users_path()`.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gyja9um7j62n2y61esrf.png) 

Essa função já busca o path (caminho) da action `:create`. Então, se algum dia o path for alterado não preciramos alterar aqui.

Você pode estar se perguntando de onde está vindo essa função. Ela vem do `ConnCase`:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oy1hv68fa13sadiu25v2.png) 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/812qq0wpo7oes7io1kko.png) 

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/klhpi3wv61mbadkxk4wl.png) 

usando alguns parâmetros:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1p2k7r8vkwxqn41xqnxt.png) 
 
Mais detalhes você encontra na documentação: https://hexdocs.pm/phoenix/routing.html#path-helpers

### Testando a função create/2 - Segundo Cenário <a name="testando-a-funcao-call-segundo-cenario"></a>

O segundo cenário é quando algum campo obrigatório não é preenchido.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zedhe3l1l43hz9oxixh7.png)

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/253lm658516b44d78oxm.png)

Copie o map do terminal e cole no lugar de "teste":
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tmzru0wz95wmauwp5r5k.png)

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ukd5fki8hxkx38h2ybx7.png)
 
Ou também você pode fazer usando igualdade:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/djlb6sav7m80g7f56i5k.png)

### Testando a função delete/2 <a name="testando-a-funcao-delete"></a>

Vamos fazer o teste desta função para conhecer uma nova funcionalidade. Para este teste vamos inserir um usuário no banco de dados. 

Em `test/support/factory.ex`:

Altere `use ExMachina` para:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jbhpwlm7ndrra7meet89.png)
 
Adicione ao mesmo arquivo:
```elixir
def user_factory do
    %User{
      age: 27,
      address: "Rua do Maiqui",
      cep: "12345678",
      cpf: "12345678901",
      email: "maiqui@tome.com.br",
      password: "123456",
      name: "Maiqui Tomé",
      id: "b721fcad-e6e8-4e8f-910b-6911f2158b4a"
    }
end
```

Em `test/rockelivery_web/controllers/users_controller_test.exs`:

```elixir
describe "delete/2" do
    test "when there is an user with the given id, deletes the user", %{conn: conn} do
      id = "b721fcad-e6e8-4e8f-910b-6911f2158b4a"
      # inserindo a struct user no banco
      insert(:user)

      response =
        conn
        |> delete(Routes.users_path(conn, :delete, id))
        |> response(:no_content)

      assert response == ""
    end
end
```

Como o retorno é um `:no_content` (uma string vazia) estamos usando a função `response` ao invés do `json_response`.

## 😎 Testando a UsersView <a name="testando-a-users-view"></a>

Crie o arquivo de teste `test/rockelivery_web/views/users_view_test.exs`.

Dessa vez não faremos `describe` pois a view só tem a função `render`.

```elixir
defmodule RockeliveryWeb.UsersViewTest do
  use RockeliveryWeb.ConnCase, async: true

  import Phoenix.View
  import Rockelivery.Factory

  alias RockeliveryWeb.UsersView

  test "renders create.json" do
    user = build(:user)

    response = render(UsersView, "create.json", user: user)

    assert %{
             message: "User created!",
             user: %Rockelivery.User{
               address: "Rua do Maiqui",
               age: 27,
               cep: "12345678",
               cpf: "12345678901",
               email: "maiqui@tome.com.br",
               id: "b721fcad-e6e8-4e8f-910b-6911f2158b4a",
               inserted_at: nil,
               name: "Maiqui Tomé",
               password: "123456",
               password_hash: nil,
               updated_at: nil
             }
           } = response
  end
end
```
## 👀 Verificando a Cobertura de Testes <a name="verificando-a-cobertura-de-testes"></a>

Agora podemos verificar a nossa cobertura de testes com o comando
```bash
$ mix test --cover
```
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5gkibbxej6vli83bgaz.png)

Agora você já pode testar o restante da aplicação :)