DEV Community

loading...
Cover image for Projeto Rockelivery: API para Pedidos em um Restaurante com Elixir e Phoenix (Parte 4)

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

Maiqui Tomé
Junior Backend Developer
・10 min read

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

Acompanhe o repositório no GitHub: https://github.com/maiquitome/rockelivery_api

Conteúdo:

🙋‍♂️ 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 :)

Discussion (0)