Continuando nosso blog(github.com/vagnerzampieri/zblog), irei de Rspec hoje para validar nossos models.
Rspec:
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:
gem 'rspec'
gem 'rspec-rails'
Se não existirem, coloque e faça um "bundle install" no terminal. Ainda no terminal instale o rspec no seu projeto fazendo um:
rails g rspec:install
Ele criará isso:
create .rspec
create spec
create spec/spec_helper.rb
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:
require 'spec_helper'
describe Post do
it "test"
end
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:
rake spec:models
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".
RED, vamos ver nossa ideia quebrar de propósito com esse exemplo abaixo:
describe Post do
it "should be not created without title" do
post = Post.new(:title => "", :body => "Texto qualquer em body", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1)
post.should be_valid
end
end
Saída:
F
Failures:
1) Post should be not created without title
Failure/Error: post.should be_valid
expected valid? to return true, got false
# ./spec/models/post_spec.rb:6:in `block (2 levels) in <top (required)>'
Finished in 0.04851 seconds
1 example, 1 failure
rake aborted!
ruby -S bundle exec rspec ./spec/models/post_spec.rb failed
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".
describe Post do
it "should be not created without title" do
post = Post.new(:title => "", :body => "Text for body", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1)
post.should_not be_valid
end
it "should be not created without body" do
post = Post.new(:title => "Title", :body => "", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1)
post.should be_valid
end
end
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:
describe Post do
before(:each) do
@post = Post.new(:title => "Title", :body => "Text for body", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1)
end
it "should be not created without title" do
@post.title = nil
@post.should_not be_valid
end
it "should be not created without body" do
@post.body = nil
@post.should_not be_valid
end
end
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.
it 'should be created with all requirements' do
@post.should be_valid
end
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.
describe User do
before(:each) do
@user = User.new(:name => 'User Test', :login => 'usertest', :email => 'test@test.com', :password => 'senha123', :password_confirmation => 'senha123' )
end
it 'should be not created without name' do
@user.name = nil
@user.should_not be_valid
end
it 'should be not created without login' do
@user.login = nil
@user.should_not be_valid
end
it 'should be not created without email' do
@user.email = nil
@user.should_not be_valid
end
it 'should be not created without password' do
@user.password = nil
@user.should_not be_valid
end
it 'should be not created if password and password_confirmation not equal' do
@user.password = 'senha123'
@user.password_confirmation = 'senha'
@user.password.should_not == @user.password_confirmation
end
it 'should be created with all requirements' do
@user.should be_valid
end
end
O único comportamento diferente é o penúltimo que testa se a senha do usuário está diferente.
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.
resources :posts, :path => 'admin/posts'
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:
rails g migration add_field_author_id_to_posts
Abra o arquivo que foi criado e faça as modificações abaixo:
class AddFieldAuthorIdToPosts < ActiveRecord::Migration
def self.up
add_column :posts, :author_id, :integer
add_index :posts, :author_id
end
def self.down
remove_index :posts, :author_id
remove_column :posts, :author_id
end
end
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.
has_many :posts, :foreign_key => "author_id"
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.
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
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.
def create
@post = Post.new params[:post]
@post.author_id = current_user.id
if @post.save
flash[:notice] = 'Post was successfully created.'
respond_with @post
else
render :action => :new
end
end
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.
index:
<th>Author</th>
<td><%= post.author.name unless post.author.nil? %></td>
show:
<p>
<b>Author:</b>
<%= @post.author.name unless @post.author.nil? %>
</p>
Para quem não conhece esse "unless" é uma negação, só vai executar se author não for nulo.
Chega por hoje, o próximo post deve ser de Rspec, mas teste de controller.
Até a próxima!!
oi Vagner,
ResponderExcluirEstou testando esse post e aprendendo Rspec.
Nao seria "rspec spec/models/" em vez de "rake spec:models" ?
Muito bom seu tutorial.
[]'s
Sergio Lima
sergiosouzalima@gmail.com
Sergio, é "rake spec:models" msm, se vc der no terminal, "rake -T" vc verá as tarefas que o seu sistema pode fazer.
ResponderExcluirEu coloquei um validates_presence_of :author_id no model do Post, como testo isso?
ResponderExcluirSaulo, como vc só está validando a presença, no before(:each) coloque o :author_id => 1 e crie assim:
ResponderExcluirit 'should be not created without author id' do
@post.author_id = nil
@post.should_not be_valid
end
e em user_spec vc pode fazer esse teste:
it 'should have many post' do
@user.save
post1 = Post.create(:title => "Title", :body => "Text for body", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1, :author_id => @user.id)
post2 = Post.create(:title => "Title 2", :body => "Text for body 2", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1, :author_id => @user.id)
@user.posts.should have(2).items
end
espero ter ajudado.
Muito bom cara me ajudou bastante =)
ResponderExcluir