rspec Parte 2 – Testando controllers

Bom dia Pessoal.

Continuando a serie de posts sobre rspec. Estou me acostumando com essa ferramenta, mas de cara já posso dizer que é fantástica, pois você consegue agilizar alguns testes como validar se os cotrollers estão respondendo como esperado, sem a necessidade de dar um refresh no browser.

Hoje irei mostrar como testar controllers.

Pelo que andei pesquisando há um grande debate quando se trata de testar controllers ou não. No meu caso eu acho valido, mas esta é a minha opinião. Isso assegura que todas as rotas e comportamentos da aplicação estejam corretos.

No meu exemplo estarei testando também os acessos ao banco usando a gem FactoryGirl para criar objetos fake. Nós estamos usando também a gem Devise para realizar as autenticações. Vamos lá.

Vou utilizar o projeto toPlay como exemplo, eu não me atenho a detalhes no teste dos controllers somente valido se estes estão respondendo como o esperado (formato, redirect, etc). Quando necessário utilizo o FactoryGirl para criar os objetos para teste.

Meu controller:

class AnswerController < ApplicationController
  def get_answer
    @answer = Alternative.find(params[:answer_id])
    respond_to do |format|
      format.json { render json: { is_correct: @answer.correct } }
    end
  end
end

Basicamente este controller me responde se a alternativa escolhida é a correta, simples. O que eu testo neste caso:

- Crio os objetos para teste nesse caso, o exercicio e uma alternativa correta e incorreta.

RSpec.describe AnswerController, :type => :controller do

  let(:exercise){ FactoryGirl.create(:exercise) }
  let(:correct){ FactoryGirl.create(:alternative) }
  let(:incorrect){ FactoryGirl.create(:alternative_incorrect) }

Descrevo o metodo a ser testado

  describe "GET #get_answer" do

Dou um contexto, neste caso, user não logado

  context 'user not logged' do

E os comportamentos esperados

it 'should redirect user' do
  sign_out
  get :get_answer, { answer_id: correct.id , format: :json }
  expect(response).to be_redirect
end

Neste caso o controller me responde com JSON então eu testo um retorno Html e Json. Não espero um retorno HTML então deve dar um erro.

    context 'when request html' do
      it {
        expect{
          get :get_answer, { exercise_id: exercise.id, answer_id: correct.id }
        }.to raise_error ActionController::UnknownFormat
      }
    end

Testo os retornos para uma resposta certa e uma errada.

    context 'when given a correct answer' do
      before :each do
        sign_in
        correct.correct = true
        correct.save
        get :get_answer, { answer_id: correct.id , format: :json }
      end

      it{ expect(response).to be_success }
      it{ expect(response.content_type).to be_eql 'application/json' }
      it{ expect(response.body).to be_include '"is_correct":true' }

    end

    context 'when given a incorrect answer' do
      before :each do
        sign_in
        incorrect.correct = false
        incorrect.save
        get :get_answer, { answer_id: incorrect.id , format: :json }
      end

      it{ expect(response).to be_success }
      it{ expect(response.content_type).to be_eql 'application/json' }
      it{ expect(response.body).to be_include '"is_correct":false' }

    end

O código completo pode ser visto em:

https://github.com/CristianOliveiraDaRosa/toPlay/blob/master/spec/controllers/answer_controller_spec.rb

Neste modelo, você pode reparar que são testes simples, pois como comentei não me atenho a muitos testes de controller, prefiro apenas validar rotas e respostas esperadas.

Espero ter ajudado.

Quando usar Mocks e Stubs?

Bom dia Pessoal.

Continuando os estudos sobre TDD e BDD, estes dias me venho a questão:
- Quando usar Mocks/Stubs?
- Quando não usar?

Então pensei, isso é uma boa questão, pois se você exagerar no uso deles seus testes podem se tornar fakes, desnecessários e/ou confusos.

O que são Mocks

Segundo Martin Flower, são instancias de objetos reais, mas treinados para dar retornos esperados. Para utiliza-los você precisa de um framework, por exemplo no Java temos o Mockito, EasyMock entre outros.

Exemplo:

   @Test
   public void testGivenNewBehaviorForToStringWhenCastItShouldReturnNewReturn()
   {
      // given
      String resultExpected = "New Return";
      MyObject instance = Mockito.mock(MyObject.class);
      Mockito.when(instance.toString()).thenReturn(resultExpected);
      
      // when
      String result = instance.toString()
      
      // then
      assertEquals(resultExpected, result);
    }

O que são Stubs

Segundo Martin Flower, são implementações de interfaces com retorno esperados para testes de integração.

Veja o exemplo, vamos supor que temos a seguinte interface:

  public interface IJob {
    public String getResult();
  }

E uma classe usando ela:

class Worker {
  private IJob job;
  
  public void setJob(IJob job)
  {
     this.job = job;
  }

  public String work()
  {
     return job.getResult();
  } 
}

Seu teste usando Stub seria assim:

 class WorkerTest extend TestCase {
     @Test
     public void testGivenNewJobWhenWorkItShouldReturnJobDone()
     {
         // given
         String resultExpected = "Job done.";
         Worker worker = new Worker();
         worker.setJob(new StubJob());

         // when
         String result = worker.work();         

         // then
         assertEquals(resultExpected, result);
     }

     class StubJob implements IJob {
         @override 
         public String getResult()
         {
            return "Job done.";
         }
     }
 }

Quando devo usar?

Teste Unitário , como o proprio nome diz, é o teste da classe/funcionalidade/comportamento esperado. Se para testar isso, você tem que se preocupar muito com dependências, acesso a banco, webservice, configurações e isto irá influênciar no resultado, talvez a saída seja o uso de mocks/stubs para emular estes retornos e testar só as funcionalidades da classe que possui estas dependências.

Quando usar um e quando usar outro?

Segundo Dave Sayer em seu artigo para spring.io (Artigo em Inglês):

Seu teste deve ser fácil de ler e manter, então utilize a ferramenta que mais se adapte a estas duas necessidades. Caso o teste começe a ficar complexo dificultando o entendimento do que está sendo testado, talves seja o momento de repensar ele.

Abraços