-
-
Save SabretWoW/6234923 to your computer and use it in GitHub Desktop.
# 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 |
Thank you!!! <3
Great stuff, thanks!
Some interesting conversations going on here about this gist. You might want to check it out.
https://www.codewithjason.com/examples-pointless-rspec-tests/
Now matchers, which starts with ensure (ensure_inclusion_in, ensure_length_of) are renamed to validate_inclusion_in, validate_length_of (55c8d09).
@segunadeleye to respond to that blog post:
Unless I’m crazy, these sorts of tests don’t actually do anything.
You may not be crazy, but you're wrong. Shoulda checks a lot of things beyond whether the association exists. It ensures that the columns that support it exist, that the destination model class exists, and that all of the types match. This type of model test is extremely valuable for testing models. There are also chainable matchers that can be added to these matchers.
But perhaps importantly, we're writing specifications, so these also describe the behavior of the model.
Unfortunately the specs in this example gist are not correctly phrased. Since they're covering multiple objects, each of the it
blocks refers to a different subject. In those cases, it
should not be used with an implicit example name. The correct phrasing is
describe ModelObject do
it { is_expected.to belong_to(:other_model_object).optional(true) }
end
or when describing a method on the object:
described ModelObject do
described '#other_model_object' do
subject(:other_model_object) { described_class.new.other_model_object }
it { is_expected.to eq(other_object) }
end
end
This ensures meaningful spec output, since it flows in English, which is the goal of rspec.
This is cool !!!
Something similar but without shoulda-matchers
?
Amazing!
Sweet, just learning and this is a great reference..... Thanks!