Kievbuy
7/11/2018 - 12:34 PM

Testing Model with Rspec

# This is a skeleton for testing models including examples of validations, callbacks,
# scopes, instance & class methods, associations, and more.
# Pick and choose what you want, as all models don't NEED to be tested at this depth.
#
# I'm always eager to hear new tips & suggestions as I'm still new to testing,
# so if you have any, please share!
#
# @kyletcarlson
#
# This skeleton also assumes you're using the following gems: 
# 
# rspec-rails: https://github.com/rspec/rspec-rails
# Shoulda-matchers: https://github.com/thoughtbot/shoulda-matchers
# shoulda-callback-matchers: https://github.com/beatrichartz/shoulda-callback-matchers
# factory_girl_rails: https://github.com/thoughtbot/factory_girl_rails

require 'spec_helper'

describe Model do
  
  it "has a valid factory" do
    # Using the shortened version of FactoryGirl syntax.
    # Add:  "config.include FactoryGirl::Syntax::Methods" (no quotes) to your spec_helper.rb
    expect(build(:factory_you_built)).to be_valid
  end

  # Lazily loaded to ensure it's only used when it's needed
  # RSpec tip: Try to avoid @instance_variables if possible. They're slow.
  let(:factory_instance) { build(:factory_you_built) }
  
  describe "ActiveModel validations" do
    # http://guides.rubyonrails.org/active_record_validations.html
    # http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/frames
    # http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/Shoulda/Matchers/ActiveModel

    # Basic validations
    it { expect(bodybuilder).to validate_presence_of(:food).with_message(/you can't get big without your protein!/) }
    it { expect(developer).to validate_presence_of(:favorite_coffee) }
    it { expect(meal).to validate_numericality_of(:price) }
    it { expect(tumblog).to validate_numericality_of(:follower_count).only_integer }
    it { expect(odd_number).to validate_numericality_of(:value).odd }
    it { expect(even_number).to validate_numericality_of(:value).even }
    it { expect(mercedes).to validate_numericality_of(:price).is_greater_than(30000) }
    it { expect(junked_car).to validate_numericality_of(:price).is_less_than_or_equal_to(500) }
    it { expect(blog_post).to validate_uniqueness_of(:title) }
    it { expect(wishlist).to validate_uniqueness_of(:product).scoped_to(:user_id, :wishlist_id).with_message("You can only have an item on your wishlist once.") }

    # Format validations
    it { expect(user).to allow_value("JSON Vorhees").for(:name) }
    it { expect(user).to_not allow_value("Java").for(:favorite_programming_language) }
    it { expect(user).to allow_value("dhh@nonopinionated.com").for(:email) }
    it { expect(user).to_not allow_value("base@example").for(:email) }
    it { expect(user).to_not allow_value("blah").for(:email) }
    it { expect(blog).to allow_blank(:connect_to_facebook) }
    it { expect(blog).to allow_nil(:connect_to_facebook) }

    # Inclusion/acceptance of values
    it { expect(tumblog).to ensure_inclusion_of(:status).in_array(['draft', 'public', 'queue']) }
    it { expect(tng_group).to ensure_inclusion_of(:age).in_range(18..35) }
    it { expect(band).to ensure_length_of(:bio).is_at_least(25).is_at_most(1000) }
    it { expect(tweet).to ensure_length_of(:content).is_at_most(140) }
    it { expect(applicant).to ensure_length_of(:ssn).is_equal_to(9) }
    it { expect(contract).to validate_acceptance_of(:terms) }  # For boolean values
    it { expect(user).to validate_confirmation_of(:password) }  # Ensure two values match
  end

  describe "ActiveRecord associations" do
    # http://guides.rubyonrails.org/association_basics.html
    # http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/frames 
    # http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/Shoulda/Matchers/ActiveRecord

    # Performance tip: stub out as many on create methods as you can when you're testing validations 
    # since the test suite will slow down due to having to run them all for each validation check.
    #
    # For example, assume a User has three methods that fire after one is created, stub them like this:
    #
    # before(:each) do
    #   User.any_instance.stub(:send_welcome_email)
    #   User.any_instance.stub(:track_new_user_signup)
    #   User.any_instance.stub(:method_that_takes_ten_seconds_to_complete)
    # end
    #
    # If you performed 5-10 validation checks against a User, that would save a ton of time.

    # Associations
    it { expect(profile).to belong_to(:user) }
    it { expect(wishlist_item).to belong_to(:wishlist).counter_cache }
    it { expect(metric).to belong_to(:analytics_dashboard).touch }
    it { expect(user).to have_one(:profile }
    it { expect(classroom).to have_many(:students) }
    it { expect(initech_corporation).to have_many(:employees).with_foreign_key(:worker_drone_id) }
    it { expect(article).to have_many(:comments).order(:created_at) }
    it { expect(user).to have_many(:wishlist_items).through(:wishlist) }
    it { expect(todo_list).to have_many(:todos).dependent(:destroy) }
    it { expect(account).to have_many(:billings).dependent(:nullify) }
    it { expect(product).to have_and_belong_to_many(:descriptors) }
    it { expect(gallery).to accept_nested_attributes_for(:paintings) }
    
    # Read-only matcher
    # http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/Shoulda/Matchers/ActiveRecord/HaveReadonlyAttributeMatcher
    it { expect(asset).to have_readonly_attribute(:uuid) }

    # Databse columns/indexes
    # http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/Shoulda/Matchers/ActiveRecord/HaveDbColumnMatcher
    it { expect(user).to have_db_column(:political_stance).of_type(:string).with_options(default: 'undecided', null: false)
    # http://rubydoc.info/github/thoughtbot/shoulda-matchers/master/Shoulda/Matchers/ActiveRecord:have_db_index
    it { expect(user).to have_db_index(:email).unique(:true)
  end

  context "callbacks" do
    # http://guides.rubyonrails.org/active_record_callbacks.html
    # https://github.com/beatrichartz/shoulda-callback-matchers/wiki
    
    let(:user) { create(:user) }

    it { expect(user).to callback(:send_welcome_email).after(:create) }
    it { expect(user).to callback(:track_new_user_signup).after(:create) }
    it { expect(user).to callback(:make_email_validation_ready!).before(:validation).on(:create) }
    it { expect(user).to callback(:calculate_some_metrics).after(:save) }
    it { expect(user).to callback(:update_user_count).before(:destroy) }
    it { expect(user).to callback(:send_goodbye_email).before(:destroy) }
  end

  describe "scopes" do
    # It's a good idea to create specs that test a failing result for each scope, but that's up to you
    it ".loved returns all votes with a score > 0" do
      product = create(:product)
      love_vote = create(:vote, score: 1, product_id: product.id)
      expect(Vote.loved.first).to eq(love_vote)
    end

    it "has another scope that works" do
      expect(model.scope_name(conditions)).to eq(result_expected)
    end
  end

  describe "public instance methods" do
    context "responds to its methods" do
      it { expect(factory_instance).to respond_to(:public_method_name) }
      it { expect(factory_instance).to respond_to(:public_method_name) }
    end

    context "executes methods correctly" do
      context "#method name" do
        it "does what it's supposed to..."
          expect(factory_instance.method_to_test).to eq(value_you_expect)
        end

        it "does what it's supposed to..."
          expect(factory_instance.method_to_test).to eq(value_you_expect)
        end
      end
    end
  end

  describe "public class methods" do
    context "responds to its methods" do
      it { expect(factory_instance).to respond_to(:public_method_name) }
      it { expect(factory_instance).to respond_to(:public_method_name) }
    end

    context "executes methods correctly" do
      context "self.method name" do
        it "does what it's supposed to..."
          expect(factory_instance.method_to_test).to eq(value_you_expect)
        end
      end
    end
  end

end