Testando Controllers
Index:
Para o texto de Controllers, vamos usar o fluxo do Desenvolvimento Orientado a Testes, no qual, primeiro os testes são feitos e só depois o objeto de teste é criado.
Para começar os testes, dentro da pasta spec criaremos uma pasta com o nome de requests e, dentro dessa pasta, criaremos o arquivo students_request_spec.rb.

Disso, pode surgir uma dúvida: 'Por que estamos dando o nome de request e não de controller, como foi feito no caso da model?'.
A resposta é simples, para conseguir testar as controllers, vamos testar os requests. Caso testemos controllers, só será possível testar as ações dos controllers, testando requests além das ações, conseguimos testar rotas, sessões e, além de tudo, é o método recomendado pelo próprio Rspec.
Sabendo disso, podemos popular nosso arquivo students_request_spec.rb. A ideia é que a sintaxe básica não seja muito diferente da forma na qual testamos as models. Podemos, então, começar assim:
require 'rails_helper'
RSpec.describe 'Students', type: :request do
end
Nossa sintaxe básica está pronta, logo podemos começar testando nossa index:
require 'rails_helper'
RSpec.describe 'Students', type: :request do
context 'GET #Index' do
it 'should show success status and render all students' do
get :index
expect(response).to have_http_status(:success)
end
end
end
Nesse teste, estamos pegando o get :index que é basicamente acessando a rota e, quando acessarmos essa rota, esperamos que nos devolva um status http de sucesso e mostre todos os estudantes criados, todavia, quando rodarmos o código, vai falhar, pois nosso index ainda não foi criado na controller.
Assim sendo, vamos criar o que falta e ver se nossa rota está funcionando.
Ainda não temos nossa Controller dos Students, então podemos criar uma com o seguinte comando no terminal:
rails generate controller Students index
Com esse comando, criamos a controller dos Students e já vem junto a ação index. Outro ponto que deve ser ressaltado é que, ao rodar esse comando, o próprio rspec vai criar a página de request dos Students, mas como nossa objetivo é aprender a usar a ferramenta, vamos fazer tudo do zero.
Ao gerar o controller dos Students, já recebemos a controller com o método index e a rota do método.
Controller:
class StudentsController < ApplicationController
def index
end
end
Rota:
Rails.application.routes.draw do
get 'students/index'
end
Com esses dois arquivos feitos, podemos fazer algumas mudanças neles para padronizar nossos testes.
No controller dos Students:
class StudentsController < ApplicationController
def index
students = Student.all
render json: students, status: :ok
end
end
Aqui temos um controller normal, a única diferença é que ele vai renderizar um json com todos os estudantes criados até então e, caso tudo dê certo, vai retornar o status de ok(200/success).
No routes:
Rails.application.routes.draw do
get 'students/index', to: "students#index", as: :student_index
end
Por fim, a mudança final no arquivo de teste de requests:
require 'rails_helper'
RSpec.describe 'Students', type: :request do
context 'GET #Index' do
it 'should show success status and render all students' do
get student_index_path
expect(response).to have_http_status(:success)
end
end
end
A única diferença é que no lugar do :index estamos chamando o caminho que definimos como o index dos estudantes.
Agora, quando rodarmos nosso rspec teremos 8 exemplos e nenhuma falha.
Como já sabemos que nossos testes de model estão certos, o que podemos fazer é rodar o seguinte comando no terminal para verificarmos só os testes do request:
rspec spec/requests/students_request_spec.rb
Com esse comando estaremos testando apenas os requests da pasta students_request_spec.rb e teremos de retorno 1 exemplo e 0 falhas, mostrando que os testes do arquivo em questão foram bem sucedidos.
Por fim, o teste do index está feito.
Create:
Com o teste do nosso método index feito, vamos criar agora o teste do create.
Primeiramente, precisaremos de um contexto e de um exemplo. O contexto é fácil, seria testar o Post do método create, já o exemplo inicial poderia ser criar uma estudante que tem todos os parâmetros válidos:
context 'POST #create' do
it 'should create a student' do
end
end
Agora que temos nosso contexto e nosso primeiro exemplo prontos, precisamos de um caminho e um método para que possamos criar nosso usuário, então, no nosso arquivo routes adicionaremos a seguinte linha:
post 'students/create', to: "students#create", as: :create_student
Com essa linha estamos falando, basicamente, que vamos criar um usuário por meio do método post e que isso será feito pelo método create da controller students, por fim, o caminho para todo esse processo é o :create_student (create_student_path).
Com nossa rota feita, precisamos do método create na controller:
def create
student = Student.new(student_params)
student.save!
render student, status: :created
rescue StandardError => e
render json: { message:'Não foi possível criar um usuário' }, status: :bad_request
end
private
def student_params
params.require(:student).permit(
'name',
'age'
)
end
Agora que temos nosso método, podemos começar, definitivamente, a fazer nossos testes do nosso create request:
context 'POST #create' do
let(:student_params) do
{name:"Teste", age:20}
end
it 'should create a student' do
post create_student_path, params: { student: student_params }
expect(response).to have_http_status(:created)
end
end
Nesse caso, estamos testando o exemplo de criarmos um estudante normal e funcional, para isso, usamos o helper do Rspec chamado de let. O let é usado para testar os parâmetros, diferente do build que estávamos usando para os testes de model, o let não pode ser invocado dentro de um it, só dentro do context e, então, conforme precisemos de alterações, vamos mudando.
Tendo entendido isso, o que estamos fazendo é criar uma variável com o nome de student_params e passando para ela um nome e uma idade, ambos válidos de acordo com nossa model. Nesse sentido, podemos seguir para o nosso exemplo.
Já no exemplo, estamos chamando um post que acessa o método de criar um estudante e usamos a palavra params para informar que o nosso método de criação vai receber alguns parâmetros e esses parâmetros seriam do tipo student(referente a model) e para os campos usamos o nome e a idade do student_params. Em suma, é como se estivéssemos tentando criar um usuário com o nome de "Teste" e a idade de 20.
Por fim, a última linha diz que esperamos que o nosso retorno tenha o status de :created.
Ao rodarmos o rspec spec/requests/students_request_spec.rb, devemos receber a seguinte saída no terminal:
2 examples, 0 failures
Tudo bem, mas como fazemos para testar um caso que o estudante não tem os parâmetros certos para ser criado?
Simples! Como podemos ver no nosso método create no controller, quando um usuário não tem o necessário para ser criado, esperamos que o resultado seja um bad_request, então, só precisamos passar parâmetros inválidos e esperar que nossa resposta seja condizente com a que passamos na controller.
it 'should not create a student' do
student_params = {name: 'oi'}
post create_student_path, params: { student: student_params }
expect(response).to have_http_status(:bad_request)
end
Dessa vez, estamos mudando o parâmetro name da variável student_params para oi e, assim, nossa variável para a ter um parâmetro inválido. Logo, nós queremos ter, como retorno, o status de bad_request, pois esse usuário não pode ser criado.
Assim sendo, logo que rodemos nosso teste teremos:
3 examples, 0 failures
Show:
Como feito nos exemplos anteriores, precisaremos, primeiramente, de uma rota, logo após, precisaremos criar nosso método show na controller. Para nosso exemplo teremos:
Rota:
get 'student/show/:id', to: 'students#show', as: :student
Método:
def show
student = Student.find(params[:id])
render json: student, status: :ok
rescue StandardError => e
render json: { message:'Não foi possível encontrar o estudante' }, status: :bad_request
end
O diferencial do método show é o find(params[:id]) e o show/:id, o que estamos dizaendo quando usamos isso é que, para ver um item em especial, precisamos do id, o id vai ser a chave que vai garantir que cada estudante seja único. Nesse sentido, precisamos passar isso também na hora de testar o método show.
context "GET #Show" do
it 'with existing student' do
estudante = create(:student)
get student_path(1)
expect(response).to have_http_status(:ok)
end
it 'with non existing student ' do
get student_path(1)
expect(response).to have_http_status(:bad_request)
end
end
O que fizemos nesses testes? No primeiro, criamos um estudante com o factory_bot, logo, ele é usado apenas no escopo local, o que significa que o estudante que criamos dentro no primeiro exemplo, só existe naquele exemplo. Após a criação do estudante, pegamos nosso caminho e jogamos o id do estudante, como tudo dentro do exemplo é local, só temos um estudante e esse estudante é o que tem o id 1.
Então, é como se tivéssemos um banco de dados vazio e criamos um student dentro da variável estudante. Agora, temos um student e ele tem o id 1. Por fim, queremos tentar acessar esse student, então, usamos o student_path (student/show/:id), essa url precisa de um id, então passamos para ela o id 1, único id que temos, assim sendo, caso consigamos acessar o usuário, a resposta deve ser um :ok.
Pro segundo exemplo, a ideia é a mesma, só que dessa vez não criamos nenhum student, ou seja, quando tentarmos acessar o student com o id=1, cairemos no caso de erro da controller, que nos retorna um :bad_request.
Caso rodemos nosso Rspec novamente, teremos como retorno no terminal:
5 examples, 0 failures
Update:
Como já passamos nos outros exemplos, aqui vamos definir, de forma rápida a rota e o método update.
Rota:
patch 'students/update/:id', to: 'students#update', as: :update_student
Para a rota, o a requisição pode ser tanto um patch quanto um put.
Método na Controller:
def update
student = Student.find(params[:id])
student.update!(student_params)
render student, status: :ok
rescue StandardError => e
render json: { message:'Não foi possível editar o estudante' }, status: :bad_request
end
Assim sendo, agora podemos fazer o nosso test do Rspec.
context "PATCH #Update" do
let(:estudante) { create(:student) }
it 'should update student info' do
atualizado = { name: 'O aluno' }
patch update_student_path(estudante), params: { student: atualizado }
estudante.reload
expect(estudante.name).to eq(atualizado[:name])
end
end
Nesse exemplo, é bem parecido com o método show, ambos precisamos do id. Então, primeiramente, criamos um estudante pelo factory bot, após isso, podemos ir para o nosso exemplo.
No nosso exemplo, a primeira coisa que fazemos é criar uma variável com o nome de atualizado e, nessa variável, simulamos o campo name com a string de 'O aluno'. Na linha seguinte, eu chamo a requisição do tipo patch e passo o caminho para atualizar o estudante e, entre parênteses, passo o estudante que quero atualizar, no nosso caso, o que foi criado no factory_bot e adicionado a variável estudante. Após isso, passo o params, para dizer o que quero atualizar, e, dentro desse params faço com que o nosso student(model) e passo para ele o que tem na variável atualizado.
Nesse sentido, o que eu fiz, brevemente, foi pegar a variável estudante e mudar o nome. Agora, precisamos verificar se o nosso estudante teve, realmente, seu nome modificado, para isso podemos usar o reload.
O reload vai recarregar a nossa variável estudante com os dados atualizados. Para que fazemos isso? a resposta está na última linha! Fazemos isso pois esperamos que o estudante.name seja igual ao nome que pegamos da variável atualizado. Ou seja, esperamos a variável estudante, após recarregada com o reload, tenha seu nome modificado para o que passamos dentro de params.
Para tentar deixar mais explicativo, podemos tentar fazer um fluxo:
Inicialmente, nosso estudante tem o nome de Aluno, mas queremos mudar esse nome, então, no nosso sistema, mudamos esse nome para O Aluno, para verificar se esse nome foi, realmente, modificado, salvamos o novo nome e recarregamos nosso estudante para verificar o novo campo, ao fazermos isso, percebemos que o nome do estudante agora é O Aluno, então, está de acordo com o esperado.
Ao rodarmos o rspec dos requests, teremos como resultado:
6 examples, 0 failures
Delete:
Por fim, agora temos que testar o request do método delete. Esse método pode parecer um pouco diferente dos demais, mas também é bem simples.
Rota:
delete "students/delete/:id", to: "students#delete", as: :delete_student
Controller:
def delete
student = Student.find(params[:id])
student.destroy!
render student, status: :ok
rescue StandardError => e
render json: { message:'Não foi possível deletar o estudante' }, status: :bad_request
end
Como já percebemos, tanto o método na controller quanto a rota pedem um id, logo isso vai ser importante na hora dos testes, assim como foi no show e no update.
Sabendo disso, podemos fazer o seguinte no Rspec:
context "DELETE #Delete" do
let(:estudante) { create(:student) }
it 'should delete the student' do
delete delete_student_path(estudante), params: { id: estudante.id }
expect(Student.find_by(id: estudante.id)).to be_nil
end
end
O teste do delete é bem parecido com o do update, passamos o estudante para o caminho de deletar um estudante e, para identificarmos de certeza qual é esse estudante, passamos seu id. Por fim, após a execução dessa linha, procuramos o id do estudante no nosso banco de dados e esperamos que esse id não seja encontrado, ou seja, to be_nil, caso isso seja verdadeiro, o teste passa e nosso método delete está funcionando.
O que podemos fazer, em conjunto com o expect, é adicionar uma linha para ver o retorno da requisição http.
context "DELETE #Delete" do
let(:estudante) { create(:student) }
it 'should delete the student' do
delete delete_student_path(estudante), params: { id: estudante.id }
expect(response).to have_http_status(:ok)
expect(Student.find_by(id: estudante.id)).to be_nil
end
end
Quando rodarmos isso, além de checarmos se o id sumiu do banco de dados, também esperamos que a resposta à requisição que fizemos seja :ok, aí saberemos que nosso teste está 100% funcional.
Podemos também adicionar um exemplo que testa quando tentarmos deletar um estudante que já foi deletado:
it 'with unvalid id' do
estudante.destroy!
delete delete_student_path(estudante), params: { id: estudante.id }
expect(response).to have_http_status(:bad_request)
end
Na primeira linha desse exemplo, apagamos o estudante antes de tentarmos deletar ele com nosso método, então, ao chegar no nosso delete, não vai ter nada para deletar, então vai para o caso de erro e teremos, como resposta, um :bad_request.
Ao rodarmos o rspec nessa pasta teremos, como retorno o seguinte:

Assim sendo, todos nossos testes estão funcionais.
Por fim, só podemos fazer uma pequena modificação no nosso arquivo de rotas que ficou bastante repetitivo.
Rails.application.routes.draw do
get 'students/index', to: "students#index", as: :student_index
post 'students/create', to: "students#create", as: :create_student
get 'students/show/:id', to: 'students#show', as: :student
patch 'students/:id', to: 'students#update', as: :update_student
delete "students/delete/:id", to: "students#delete", as: :delete_student
end
Sempre repetimos o students, então podemos usar um scope para evitar tanta repetição. No fim, ficaria assim:
Rails.application.routes.draw do
scope 'students/' do
get 'index', to: "students#index", as: :student_index
post 'create', to: "students#create", as: :create_student
get 'show/:id', to: 'students#show', as: :student
patch 'update/:id', to: 'students#update', as: :update_student
delete 'delete/:id', to: 'students#delete', as: :delete_student
end
end
Agora temos um arquivo de rotas mais organizado e nossos testes do rspec continuam funcionando.
Last updated
Was this helpful?