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 :

  • The default factory should generate a valid object
  • Data that fail validations should not be valid
  • Class and instance methods perform as expected


  • 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.rbrequire '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

    Made with Slides.com