Essa é a quarta parte do projeto Rockelivery. Esse projeto faz parte do Bootcamp da Rocketseat, ministrado pelo professor Rafael Camarda.
Conteúdo:
- 🙋♂️ Introdução
- 🕺 Testando o User Schema
- 🤖 Refatorando com ExMachina
- ➕ Testando o módulo Users.Create
- 🏭 Refatorando a Factory
- 🕹️ Testando o UsersController
- 😎 Testando a UsersView
- 👀 Verificando a Cobertura de Testes
🙋♂️ 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
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:
Com o Rockelivery.DataCase
também teremos a tradução dos erros do changeset:
🕺 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
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:
Então, neste caso não faremos uma igualdade (==). Para este caso, a melhor forma de fazermos o teste é realizando Pattern Matching (=).
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:
Vamos agora fazer o teste passar, colocando os dados iguais nos dois lados:
Agora, ao rodarmos o comando mix test
, podemos ver que o teste passa com sucesso!
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
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:
Vamos colocar os nomes iguais pra ver o teste passar:
Agora o segundo cenário está finalizado:
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.
Agora vamos ver o teste falhar:
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:
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
:
Vamos agora alterar o nosso teste para ele passar:
Terceiro teste finalizado com sucesso!
🤖 Refatorando com ExMachina
Crie o arquivo test/support/factory.ex
:
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
Em test/rockelivery/user_test.exs
:
Importe o módulo Rockelivery.Factory
:
Executamos o comando mix test
e tudo continua funcionando como antes:
➕ 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
🏭 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
:
Após essa alteração, ao executarmos o comando mix test
percebermos que precisamos refatorar alguns testes:
Vamos refatorar em test/rockelivery/users/create_test.exs
:
Vamos para o segundo teste que precisa ser refatorado:
Em test/rockelivery/user_test.exs
:
Agora ao executarmos o comando mix test
:
🕹️ 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
.
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
Vamos entender esse código:
-
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
...
- Existem também as funções
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:
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
:
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.
Ao executarmos mix test
verificamos que os testes estão passando com sucesso:
Refatorando
Altere o seu código usando a função Routes.users_path()
.
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
:
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.
Copie o map do terminal e cole no lugar de "teste":
Ou também você pode fazer usando igualdade:
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
:
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
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
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
👀 Verificando a Cobertura de Testes
Agora podemos verificar a nossa cobertura de testes com o comando
$ mix test --cover
Agora você já pode testar o restante da aplicação :)
Top comments (0)