Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
# 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.