Testando Controllers Versionadas

Ok, testamos nossa controller, mas e no caso de termos uma controller versionada. O teste seria a mesma coisa? Não exatamente a mesma coisa, mas é bem parecido.

Para aprendermos como fazer testes de uma controller versionada precisamos, primeiro, versionar nossa controller atual, para isso, dentro da pasta controller criaremos uma pasta chamada de api e dentro desta pasta criaremos as versões, nossa versão inicial será a v1 e, dentro da pasta v1, colocaremos nossa controller atual.

Caso rodemos o Rspec agora todos os testes falharão, isso porque precisamos fazer algumas modificações, a primeira modificação pontual seria na controller:

class Api::V1::StudentsController < ApplicationController
  def index
    students = Student.all
    render json: students, status: :ok
  end

  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

  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

  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

  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

  private 
  
  def student_params
    params.require(:student).permit(
      'name',
      'age'
    )
  end
end

Assim sendo, a diferença está na primeira linha. Assim como criamos, dentro da pasta controller, a pasta api e a v1, adicionamos na class o Api::V1 precedendo o nome da controller, isso nos diz, basicamente, que é uma Api e está na versão 1.

Depois dessas mudanças precisamos mudar também o nosso arquivo de rotas:

Rails.application.routes.draw do
  namespace 'api' do 
    namespace 'v1' 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
  end
end

Nesse arquivo nós, basicamente, adicionamos mais dois caminhos para a rota o api e o v1 e aí, dentro desses namespaces colocamos as rotas que tínhamos definido anteriormente. Nesse sentido, tanto nossas rotas quanto nossa controller estão prontos.

Por fim, só falta arrumarmos o Rspec para começarmos a testar nossas requisições. Então, primeiramente, dentro da pasta request criamos a pasta api e dentro desta criamos a pasta v1, assim como fizemos com a controller.

require 'rails_helper'

RSpec.describe Api::V1::StudentsController, 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

    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

        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
    end

    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

    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

    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

        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
    end
end

É importante ressaltar que os testes da model nadam mudam, todos continuam funcionais, agora para testarmos o teste unicamente dos requests precisaremos de um novo comando:

rspec spec/requests/api/v1/students_request_spec.rb

Nada muito complicado, é só seguir o caminho das pastas até o request que queremos, o único problema é que, ao rodarmos esse teste não vai dar certo de novo, então, vamos ter que arrumar todos nossos testes para que funcionem em uma api versionada.

Index

Para o teste de index podemos arrumar da seguinte forma:

context 'GET #Index' do 
        it 'should show success status and render all students' do 
            get '/api/v1/students/index'
            expect(response).to have_http_status(:success)
        end
end

A única mudança é na forma que chamamos a rota, porque adicionamos o /api/v1/ para acharmos o caminho correto, só com o student_index_path não conseguiremos acessar a rota da maneira certa. Por fim, ao rodarmos o teste assim, o resultado será correto.

Para verificarmos só a saída desse teste, comentei os outros testes, logo, ao rodarmos o rspec teremos como resultado:

1 example, 0 failures

Create

O teste da requisição create é a mesma coisa, só precisamos mudar a rota:

context 'POST #Create' do 
        let(:student_params) do 
            {name:"Teste", age:20}
        end

        it 'should create a student' do
            post '/api/v1/students/create', params: { student: student_params }
            expect(response).to have_http_status(:created)
        end

        it 'should not create a student' do
            student_params = {name: 'oi'}
            post '/api/v1/students/create', params: { student: student_params }
            expect(response).to have_http_status(:bad_request)
        end
end

Então, percebe-se que é tudo igual, a única diferença é mudar a rota.

Ao rodarmos o Rspec, teremos a seguinte saída:

3 examples, 0 failures

Show

No show, o teste vai ser um pouco diferente dos anteriores, pois precisaremos passar o id e, agora que não podemos pegar o caminho por meio do path, precisamos pensar em alguma outra forma de passar o id na requisição.

context "GET #Show" do
        it 'with existing student' do 
             estudante = create(:student)
             get "/api/v1/students/show/#{estudante.id}"
             expect(response).to have_http_status(:ok)
         end

        it 'with non existing student ' do 
            get "/api/v1/students/show/1"
            expect(response).to have_http_status(:bad_request)
        end
end

O que mudou desse método para os demais está, primeiramente, em chamarmos o id da nossa variável diretamente na rota e também no fato de precisarmos usar aspas duplas (") para conseguirmos fazer isso, pois com aspas simples (') procuraria a rota literal. No exemplo de um estudante que não existe, eu coloquei o '1' porque eu sei que não criamos nenhum estudante para esse exemplo, ou seja, ao tentarmos achar um estudante com o id de 1, nada encontraremos. Caso façamos o mesmo no primeiro exemplo e colocarmos 1 no lugar de #{estudante.id} também funcionaria, pois no exemplo de cima criamos um estudante, mas não é tão legal fazermos assim.

Update

O update é bem parecido com o show, a diferença é o params que também usamos no Update não versionado.

context "PATCH #Update" do 
        let(:estudante) { create(:student) }

        it 'should update student info' do 
            atualizado = { name: 'O aluno' }
            patch "/api/v1/students/update/#{estudante.id}", params: { student: atualizado }
            estudante.reload
            expect(estudante.name).to eq(atualizado[:name])
        end
end

Então, mais uma vez utilizamos o #{estudante.id}, só que passamos o params que queremos que seja passado para o estudante, assim como foi feito no método anterior, mais uma vez, a única mudança é na passagem da rota.

Se quisermos fazer um método para testar o caso de falha podemos fazer o seguinte:

it 'should not update student info' do 
            atualizado = { name: 'O aluno' }
            patch "/api/v1/students/update/2", params: { student: atualizado }
            estudante.reload
            expect(response).to have_http_status(:bad_request)
end

Bem simples, não muda muita coisa do que já vimos.

Delete

Por fim, o delete é a mesma coisa:

context "DELETE #Delete" do 
        let(:estudante) { create(:student) }
        it 'should delete the student' do 
            delete "/api/v1/students/delete/#{estudante.id}", params: { id: estudante.id }
            expect(response).to have_http_status(:ok)
            expect(Student.find_by(id: estudante.id)).to be_nil
        end

        it 'with unvalid id' do
            estudante.destroy!
            delete "/api/v1/students/delete/2", params: { id: estudante.id }
            expect(response).to have_http_status(:bad_request)
        end
end

Ao rodarmos o comando rspec spec/requests/api/v1/students_request_spec.rb, o que receberemos de retorno no terminal será:

Note que, nesse caso, temos 9 exemplos funcionais porque adicionamos o teste do caso de falha do update.

Caso mude de versão, só vai ser necessário criar a pasta v2 e mudar as classes e aí continuará funcional.

Last updated