Rspec Testing
Agenda
1. To Test Or Not to test
2. testing & rails
3. rspec
4. Model specs
to test or not to test
What is Testing ?!
alpha & beta tester
why automated testing !
Testing Philosophies
1. no testing
2. waterfull testing
3. Agile testing
4. test-DRIVEN DEVELOPMENT
tDD
5. BEHAVIOR-DRIVEN DEVELOPMENT
BDD
Testing Levels
unit test
Integration test
system test
testing & rails
ruby 1.8 : test::unit
ruby 1.9 : minitest
testing is the default
$ rails new newapp
test Database
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: sqlite3
database: db/test.sqlite3
pool: 5
timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
pool: 5
timeout: 5000
Rspec
syntax
# game_spec.rb
describe Game do
describe "#score" do
it "returns 0 for all gutter game" do
game = Game.new
20.times { game.roll(0) }
game.score.should == 0
end
end
end
describe & context
Describe : for things
Context : for states
describe User do
describe "Password Validation" do
context "not-invited user" do .... end
context "invited user" do
it "requires password" do .... end
it "rejects short passwords" do end
end
end
end
Expect & should
it "requires password" do
short = "a" * 5
hash = @attr.merge(:password => short, ...)
u = User.new(hash)
expect(u).not_to be_valid
end
it "requires password" do
short = "a" * 5
hash = @attr.merge(:password => short, ...)
u = User.new(hash)
u.should_not be_valid
end
folder Structure
Rails Project : ~~ spec ~~~~ models ~~~~ controllers ~~~~ views ~~~~ routing ~~~~ requests ~~~~ features [require the capybara ] ~~~~ lib ~~~~ helpers ~~~~ factories ~~~~ etc ...
Model Specs
Model specs
should test the following :
Example
#spec/models/post.rb
require "spec_helper" describe Post do context "with 2 or more comments" do it "orders them in reverse chronologically" do post = Post.create! comment1 = post.comments.create!(:body => "first comment") comment2 = post.comments.create!(:body => "second comment") expect(post.reload.comments).to eq([comment2, comment1]) end end end
Validations
# spec/models/contact.rb
require 'spec_helper'
describe Contact do
it "has a valid factory" it "is invalid without a firstname" it "is invalid without a lastname"
end
Factory Girl
factories
Simple, flexible, building blocks for test data.
gem install factory_girl_rails
# spec/factories/user.rb
FactoryGirl.define do factory :user do |f| f.firstname "John" f.lastname "Doe" end end
# ...
f.activation_code { User.generate_activation_code }
f.date_of_birth { 21.years.ago }
f.email { "#{firstname}.#{lastname}@example.com".downcase }
Faker
# spec/factories/user.rb
require 'faker'
FactoryGirl.define do
factory :user do |f|
f.firstname { Faker::Name.first_name }
f.lastname { Faker::Name.last_name }
end
end
factory girl cont.
# Returns an instance that's not saved
user = FactoryGirl.build(:user)
# Returns a saved instance
user = FactoryGirl.create(:user)
# Returns a hash of attributes that can be used to build an instance
attrs = FactoryGirl.attributes_for(:user)
# Build a User instance and override the first_name property
user = FactoryGirl.build(:user, first_name: "Joe")
user.first_name
# => "Joe"
fg :: Associations & Aliases
factory :user, aliases: [:commenter] do
first_name "John"
last_name "Doe"
date_of_birth { 18.years.ago }
end
factory :post do
association :author, factory: :user
title "How to read a book effectively"
body "There are five steps involved."
end
factory :comment do
commenter
body "Great article!"
end
fg :: inheritance
factory :post do
title "A title"
factory :approved_post do
approved true
end
end
=
factory :post do
title "A title"
end
factory :approved_post, parent: :post do
approved true
end
approved_post = FactoryGirl.create(:approved_post)
approved_post.title # => "A title"
approved_post.approved # => true
fg :: Sequences
# Defines a new sequence
FactoryGirl.define do
sequence :email do |n|
"person#{n}@example.com"
end
end
FactoryGirl.generate :email
# => "person1@example.com"
FactoryGirl.generate :email
# => "person2@example.com"
factory :user do
email
end
=
factory :user do
sequence(:email) {|n| "person#{n}@example.com" }
end
fg :: traits
FactoryGirl.define do
factory :show_cast do
character_name "show cast name"
character_bio "show cast bio"
show_id 1
person
trait :playself do
playself 1
endfactoty playself_show_cast, traits: [:playself]
end
end
show_cast = FactoryGirl.create(:show_cast, :playself)
show_cast = FactoryGirl.create(:playself_show_cast)
fg :: callbacks
factory :user do
after(:build) { |user| do_something_to(user) }
after(:create) { |user| do_something_else_to(user) }
end
test validations
it "should have valid factory" do
FactoryGirl.build(:user).should be_valid
end
it "should require a username" do
FactoryGirl.build(:user, :username => "").should_not be_valid
end
Shoulda
describe Post do
it { should validate_presence_of(:title) }
it { should validate_uniqueness_of(:title) }
it { should validate_presence_of(:body).with_message(/wtf/) }
it { should validate_presence_of(:title).on(:update) }
it { should validate_numericality_of(:user_id) }
end
describe User do
it { should_not allow_value("blah").for(:email) }
it { should allow_value("a@b.com").for(:email) }
it { should ensure_inclusion_of(:age).in_range(1..100) }
it { should_not allow_mass_assignment_of(:password) }
it { should have_secure_password }
end
Instance Methods test
# app/models/contact.rb
def name [firstname, lastname].join " " end
# spec/models/contact_spec.rb
it "returns a contact's full name as a string" do contact = Factory(:contact, firstname: "John", lastname: "Doe") expect(contact.name).to eq("John Doe") #contact.name.should == "John Doe" end
class methods test
# app/models/contact.rb
def self.by_letter(letter) where("lastname LIKE ?", "#{letter}%").order(:lastname) end
# spec/models/contact_spec.rb
require 'spec_helper'
describe Contact do it "returns a sorted array of results that match" do smith = Factory(:contact, lastname: "Smith") jones = Factory(:contact, lastname: "Jones") johnson = Factory(:contact, lastname: "Johnson") Contact.by_letter("J").should == [johnson, jones] end end
Controller specs
controller specs
1.why test Controller specs.
2.what to test :
- Rendered templates
- Redirects
- Instance variables assigned in the controller to be shared with the view
-
Cookies sent back with the response
expects :
expect(response.status).to eq(200)
expect(response).to render_template(wraps assert_template)
expect(response).to redirect_to(wraps assert_redirected_to)
expect { get :index }.to raise_error(AccessDenied)
example
it "redirects to the home page upon save" do
post :create, contact: Factory.attributes_for(:contact)
expect(response).to redirect_to(root_url)
end
structure
# spec/controllers/contacts_controller_spec.rb
require 'spec_helper'
describe ContactsController do
describe "GET #index" do
it "populates an array of contacts"
it "renders the :index view"
end
describe "GET #show" do
it "assigns the requested contact to @contact"
it "renders the :show template"
end
describe "GET #new" do
it "assigns a new Contact to @contact"
it "renders the :new template"
end
describe "POST #create" do
context "with valid attributes" do
it "saves the new contact in the database"
it "redirects to the home page"
end
context "with invalid attributes" do
it "does not save the new contact in the database"
it "re-renders the :new template"
end
end
end
testing get methods
describe "GET #index" do
it "populates an array of contacts" do
contact = Factory(:contact)
get :index
expect(assigns(:contacts)).to eq([contact])
end
it "renders the :index view" do
get :index
expect(response).to render_template("index")
end
end
describe "GET #show" do
it "assigns the requested contact to @contact" do
contact = Factory(:contact)
get :show, id: contact
expect(assigns(:contact)).to eq([contact])
end
it "renders the #show view" do
get :show, id: Factory(:contact)
expect(response).to render_template("show")
end
end
testing post methods
describe "POST create" do
context "with valid attributes" do
it "creates a new contact" do
post :create, contact: Factory.attributes_for(:contact)
expect(response).to change(Contact,:count).by(1)
end
it "redirects to the new contact" do
post :create, contact: Factory.attributes_for(:contact)
response.should redirect_to Contact.last
end
end
context "with invalid attributes" do
it "does not save the new contact" do
post :create, contact: Factory.attributes_for(:xcontact)
expect(response).to_not change(Contact,:count)
end
it "re-renders the new method" do
post :create, contact: Factory.attributes_for(:xcontact)
response.should render_template :new
end
end
end
testing put methods
describe 'PUT update' do
before :each do
@contact = Factory(:contact, firstname: "Lawrence", lastname:
"Smith")
end
....
cont.
context "valid attributes" do
it "located the requested @contact" do
put :update, id: @contact, contact:
Factory.attributes_for(:contact)
assigns(:contact).should eq(@contact)
end
it "changes @contact's attributes" do
put :update, id: @contact, contact:
Factory.attributes_for(:contact, firstname: "Larry",
lastname: "Smith")
@contact.reload
@contact.firstname.should eq("Larry")
@contact.lastname.should eq("Smith")
end
it "redirects to the updated contact" do
put :update, id: @contact,
contact: Factory.attributes_for(:contact)
response.should redirect_to @contact
end
end
cont.
context "invalid attributes" do
it "locates the requested @contact" do
put :update, id: @contact,
contact: Factory.attributes_for(:invalid_contact)
assigns(:contact).should eq(@contact)
end
it "does not change @contact's attributes" do
put :update, id: @contact,
contact: Factory.attributes_for(:contact,
firstname: "Larry",lastname: nil)
@contact.reload
@contact.firstname.should_not eq("Larry")
@contact.lastname.should eq("Smith")
end
it "re-renders the edit method" do
put :update, id: @contact,
contact: Factory.attributes_for(:invalid_contact)
response.should render_template :edit
end
end
testing Delete methods
describe 'DELETE destroy' do
before :each do
@contact = Factory(:contact)
end
it "deletes the contact" do
expect{ delete :destroy, id: @contact }.to
change(Contact,:count).by(-1)
end
it "redirects to contacts#index" do
delete :destroy, id: @contact
expect(response).to redirect_to(contacts_url)
end
end
Test all possible cases
BAD
it 'shows the resource'
good
describe '#get' do
context 'when resource is found' do
it 'responds with 200'
it 'shows the resource'
end
context 'when resource is not found' do
it 'responds with 404'
end
context 'when resource is not owned' do
it 'responds with 404'
end
end
Routing specs
expect(:get => "/articles/2012/11/when-to-use-routing-specs").to route_to(
:controller => "articles",
:month => "2012-11",
:slug => "when-to-use-routing-specs"
)
expect(:delete => "/accounts/37").not_to be_routable
Rspec Testing::1
By mohheader
Rspec Testing::1
- 290