DEV Community

Cover image for 💧🍔 Projeto Rockelivery: API para Pedidos em um Restaurante com Elixir e Phoenix (Parte 4)
Dev Maiqui 🇧🇷
Dev Maiqui 🇧🇷

Posted on • Edited on

1 1

💧🍔 Projeto Rockelivery: API para Pedidos em um Restaurante com Elixir e Phoenix (Parte 4)

Essa é a quarta parte do projeto Rockelivery. Esse projeto faz parte do Bootcamp da Rocketseat, ministrado pelo professor Rafael Camarda.

Caso queira adquirir os cursos da Rocketseat com o meu cupom de desconto Acesse esse link

Conteúdo:

A Jornada do Autodidata em Inglês

🙋‍♂️ Introdução

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

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:

use Rockelivery.DataCase, async: true
Enter fullscreen mode Exit fullscreen mode

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
Com o Rockelivery.DataCase também teremos a tradução dos erros do changeset:
image

🕺 Testando o User Schema

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

Neste teste teremos 3 cenários:

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
Enter fullscreen mode Exit fullscreen mode

Primeiro Cenário

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:
imageimage

Então, neste caso não faremos uma igualdade (==). Para este caso, a melhor forma de fazermos o teste é realizando Pattern Matching (=).

image

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

Vamos agora fazer o teste passar, colocando os dados iguais nos dois lados:

image

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

image

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

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
Enter fullscreen mode Exit fullscreen mode

Segundo Cenário

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

Como esperado o teste falha:
image

Vamos colocar os nomes iguais pra ver o teste passar:
image

Agora o segundo cenário está finalizado:
image

Terceiro Cenário

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

Agora vamos ver o teste falhar:
image

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

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

Vamos agora alterar o nosso teste para ele passar:
image

Terceiro teste finalizado com sucesso!
image

🤖 Refatorando com ExMachina

Crie o arquivo test/support/factory.ex:
image

O seu conteúdo deve ser semelhante:

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
Enter fullscreen mode Exit fullscreen mode

Em test/rockelivery/user_test.exs:

Importe o módulo Rockelivery.Factory:
image

Primeiro Cenário:
image

Segundo Cenário:
image

Terceiro Cenário:
image

Executamos o comando mix test e tudo continua funcionando como antes:
image

➕ Testando o módulo Users.Create

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:

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
Enter fullscreen mode Exit fullscreen mode

🏭 Refatorando a Factory

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

Após essa alteração, ao executarmos o comando mix test percebermos que precisamos refatorar alguns testes:
image

Vamos refatorar em test/rockelivery/users/create_test.exs:
image

Vamos para o segundo teste que precisa ser refatorado:
image

Em test/rockelivery/user_test.exs:
image

Agora ao executarmos o comando mix test:
image

🕹️ Testando o UsersController

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

Testando a função create/2 - Primeiro Cenário

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:

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
Enter fullscreen mode Exit fullscreen mode

Vamos entender esse código:

image

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

image

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

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

Ao executarmos mix test verificamos que os testes estão passando com sucesso:
image

Refatorando

Altere o seu código usando a função Routes.users_path().
image

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
image

image

usando alguns parâmetros:
image

Mais detalhes você encontra na documentação: https://hexdocs.pm/phoenix/routing.html#path-helpers

Testando a função create/2 - Segundo Cenário

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

image

image

Copie o map do terminal e cole no lugar de "teste":
image

image

Ou também você pode fazer usando igualdade:
image

Testando a função delete/2

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

Adicione ao mesmo arquivo:

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
Enter fullscreen mode Exit fullscreen mode

Em test/rockelivery_web/controllers/users_controller_test.exs:

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
Enter fullscreen mode Exit fullscreen mode

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

😎 Testando a UsersView

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.

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
Enter fullscreen mode Exit fullscreen mode

👀 Verificando a Cobertura de Testes

Agora podemos verificar a nossa cobertura de testes com o comando

$ mix test --cover
Enter fullscreen mode Exit fullscreen mode

image

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

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay