Ruby on Rails do zero, Teste de model com Rspec
Autor: Vagner Zampieri | Publicado: 2011-03-18T07:13:00-07:00

&nbsp; Continuando nosso blog(github.com/vagnerzampieri/zblog), irei de Rspec hoje para validar nossos models.
&nbsp; 
&nbsp; Rspec:
&nbsp; 
&nbsp; Vamos fazer então nossos primeiros teste, algo bem simples, vai ser teste de validação de modelo. Certifique que as gems abaixo estão no seu Gemfile:
&nbsp; 
&nbsp; gem 'rspec'
&nbsp; gem 'rspec-rails'
&nbsp; 
&nbsp; Se não existirem, coloque e faça um "bundle install" no terminal. Ainda no terminal instale o rspec no seu projeto fazendo um:
&nbsp; 
&nbsp; rails g rspec:install
&nbsp; 
&nbsp; Ele criará isso: 
&nbsp; 
&nbsp; create&nbsp; .rspec
&nbsp; create&nbsp; spec
&nbsp; create&nbsp; spec/spec_helper.rb
&nbsp; 
&nbsp; Entre na pasta "spec" que ele criou, crie uma pasta chamada "models", dentro dela crie o arquivo "post_spec.rb", e adicione o conteúdo abaixo:
&nbsp; 
&nbsp; require 'spec_helper'

&nbsp; describe Post do
&nbsp;&nbsp;&nbsp; it "test"
&nbsp; end
&nbsp; 
&nbsp; Primeiro eu dou um "require 'spec_helper'" para ele chamar o arquivo de mesmo nome que está na pasta "spec" para que eu possa utilizar o Rspec. Eu vou testar comportamento e não método, vou usar BDD, testar comportamento e verificar se a ação que o usuário efetuou está de acordo. Para você ver alguma saída rode no terminal:
&nbsp; 
&nbsp; rake spec:models
&nbsp; 
&nbsp; Vocé irá ver uma saída em amarelo, e um contador avisando que existe 1 exemplo, e ele está pendente. Isso é só um teste para ver se o Rspec está funcionando da forma certa. A primeira coisa que testaremos é se o Post pode ser criado sem título, sempre tenha em mente, "red", "green", "refactory".
&nbsp; 
&nbsp; RED, vamos ver nossa ideia quebrar de propósito com esse exemplo abaixo:
&nbsp; 
&nbsp; describe Post do
&nbsp;&nbsp;&nbsp; it "should be not created without title" do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post = Post.new(:title =&gt; "", :body =&gt; "Texto qualquer em body", :publication =&gt; Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled =&gt; 1)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.should be_valid
&nbsp;&nbsp;&nbsp; end
&nbsp; end
&nbsp; 
&nbsp; Saída:
&nbsp; 
&nbsp; F

&nbsp; Failures:

&nbsp;&nbsp;&nbsp; 1) Post should be not created without title
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Failure/Error: post.should be_valid
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; expected valid? to return true, got false
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # ./spec/models/post_spec.rb:6:in `block (2 levels) in &lt;top (required)&gt;'

&nbsp; Finished in 0.04851 seconds
&nbsp; 1 example, 1 failure
&nbsp; rake aborted!
&nbsp; ruby -S bundle exec rspec ./spec/models/post_spec.rb failed
&nbsp; 
&nbsp; Pra quem não conhece Rspec, eu descrevou uma situação quem não pode acontecer no "it", mas faço ao contrário no final "post.should be_valid", eu digo que ele "deve validar". Se você ler a saída vai ver o que estou dizendo. Para nossa espectativa funcionar troque o "should", por "should_not" e irá ver que a saída será GREEN. Faça o mesmo teste para "body", já que são os únicos que validamos no post passado em "app/models/post.rb".
&nbsp; 
&nbsp; describe Post do
&nbsp;&nbsp;&nbsp; it "should be not created without title" do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post = Post.new(:title =&gt; "", :body =&gt; "Text for body", :publication =&gt; Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled =&gt; 1)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.should_not be_valid
&nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; it "should be not created without body" do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post = Post.new(:title =&gt; "Title", :body =&gt; "", :publication =&gt; Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled =&gt; 1)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.should be_valid
&nbsp;&nbsp;&nbsp; end
&nbsp; end
&nbsp; 
&nbsp; Ocorreu o mesmo RED de antes, faça ele ficar GREEN e vamos agora para o REFACTORY. Esse código está meio repetitivo, vamos deixar ele mais elegante:
&nbsp; 
&nbsp; describe Post do
&nbsp;&nbsp;&nbsp; before(:each) do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @post = Post.new(:title =&gt; "Title", :body =&gt; "Text for body", :publication =&gt; Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled =&gt; 1)
&nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; it "should be not created without title" do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @post.title = nil
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @post.should_not be_valid
&nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; it "should be not created without body" do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @post.body = nil
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @post.should_not be_valid
&nbsp;&nbsp;&nbsp; end
&nbsp; end
&nbsp; 
&nbsp; Eu criei um "before(:each)" que criará um post, dentro de cada "it" eu passo o valor que eu quero testar como "nil", irá dar o efeito que quero. Crio um último "it" para todos os dados passem.
&nbsp; 
&nbsp; it 'should be created with all requirements' do
&nbsp;&nbsp;&nbsp; @post.should be_valid
&nbsp; end
&nbsp; 
&nbsp; Agora façam os mesmos tipos de teste com User, depois conferem se ficou igual ao que eu fiz. Lembre que o Devise faz validação de campos e que você só precisa testar os campos que exigem validação. E também não existe um certo, e sim o que você acha necessário.
&nbsp; 
&nbsp; describe User do
&nbsp;&nbsp;&nbsp; before(:each) do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user = User.new(:name =&gt; 'User Test', :login =&gt; 'usertest', :email =&gt; 'test@test.com', :password =&gt; 'senha123', :password_confirmation =&gt; 'senha123' )
&nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; it 'should be not created without name' do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.name = nil
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.should_not be_valid
&nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; it 'should be not created without login' do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.login = nil
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.should_not be_valid
&nbsp;&nbsp;&nbsp; end

&nbsp;&nbsp;&nbsp; it 'should be not created without email' do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.email = nil
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.should_not be_valid
&nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; it 'should be not created without password' do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.password = nil
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.should_not be_valid
&nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; it 'should be not created if password and password_confirmation not equal' do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.password = 'senha123'
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.password_confirmation = 'senha'
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.password.should_not == @user.password_confirmation
&nbsp;&nbsp;&nbsp; end
&nbsp;&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; it 'should be created with all requirements' do
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @user.should be_valid
&nbsp;&nbsp;&nbsp; end&nbsp; 
&nbsp; end
&nbsp; 
&nbsp; O único comportamento diferente é o penúltimo que testa se a senha do usuário está diferente.
&nbsp; 
&nbsp; Vamos voltar a fazer modificações no Post. Temos agora dois layouts simulando a "home" de um site e "admin", mas nossa rota não indica que "posts" faz parte de um admin, então vá no arquivo routes e modifique o path da rota, e depois é só carregar a página, veja como vai ficar abaixo.
&nbsp; 
&nbsp; resources :posts, :path =&gt; 'admin/posts'
&nbsp; 
&nbsp; Vamos adicionar autor na nossa tabela posts, já que todo post precisa de um autor, e esse autor será o usuário que estiver armazenado na nossa sessão. Primeiro crie um migração nova que adicionará o campo "author_id" na tabela "posts", será um relacionamento de um autor para muitos posts, e somente um post para um usuário. Crie a migração desta forma:
&nbsp; 
&nbsp; rails g migration add_field_author_id_to_posts
&nbsp; 
&nbsp; Abra o arquivo que foi criado e faça as modificações abaixo:
&nbsp; 
&nbsp; class AddFieldAuthorIdToPosts &lt; ActiveRecord::Migration
&nbsp;&nbsp;&nbsp; def self.up
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; add_column :posts, :author_id, :integer
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; add_index :posts, :author_id
&nbsp;&nbsp;&nbsp; end

&nbsp;&nbsp;&nbsp; def self.down
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; remove_index :posts, :author_id
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; remove_column :posts, :author_id
&nbsp;&nbsp;&nbsp; end
&nbsp; end
&nbsp; 
&nbsp; Está adicionando uma nova coluna e um novo índice na tabela posts. Agora abra o model "user" e adicione a linha abaixo, na primeira linha depois da classe ter sido criada.
&nbsp; 
&nbsp; has_many :posts, :foreign_key =&gt; "author_id"
&nbsp; 
&nbsp; Está dizendo que um usuário terá muitos posts, e sua chave estrangeira é "author_id". Agora no model "post" adicione a linha abaixo, na primeira linha depois da classe ter sido criada.
&nbsp; 
&nbsp; belongs_to :author, :class_name =&gt; "User", :foreign_key =&gt; "author_id"
&nbsp; 
&nbsp; Está dizendo que a chave estrangeira "author_id" pertence a classe "User". Como sempre o post será do usuário logado ao sistema que é um autor, a associação de autor e post não será interferida neste momento. Abra o controller "posts" e faça o código abaixo no método create.
&nbsp; 
&nbsp; def create
&nbsp;&nbsp;&nbsp; @post = Post.new params[:post]
&nbsp;&nbsp;&nbsp; @post.author_id = current_user.id
&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp; if @post.save
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; flash[:notice] = 'Post was successfully created.'
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; respond_with @post
&nbsp;&nbsp;&nbsp; else
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; render :action =&gt; :new
&nbsp;&nbsp;&nbsp; end
&nbsp; end
&nbsp; 
&nbsp; Você não viu errado, é só adicionar essa linha mesmo, antes de salvar eu adiciono o id do usuário atual no campo "author_id", esse "current_user" é um método do Devise, retorna um hash com os dados do usuário. Nas views "index" e "show" adicione a saída.
&nbsp; 
&nbsp; index:
&nbsp; 
&nbsp; &lt;th&gt;Author&lt;/th&gt;
&nbsp; 
&nbsp; &lt;td&gt;&lt;%= post.author.name unless post.author.nil? %&gt;&lt;/td&gt;
&nbsp; 
&nbsp; show:
&nbsp; 
&nbsp; &lt;p&gt;
&nbsp;&nbsp;&nbsp; &lt;b&gt;Author:&lt;/b&gt;
&nbsp;&nbsp;&nbsp; &lt;%= @post.author.name unless @post.author.nil? %&gt;
&nbsp; &lt;/p&gt;
&nbsp; 
&nbsp; Para quem não conhece esse "unless" é uma negação, só vai executar se author não for nulo.
&nbsp; 
&nbsp; Chega por hoje, o próximo post deve ser de Rspec, mas teste de controller.
&nbsp; Até a próxima!!
&nbsp;