Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Rails Rspec model testing skeleton & cheat sheet using rspec-rails, shoulda-matchers, shoulda-callbacks, and factory_girl_rails. Pretty much a brain dump of examples of what you can (should?) test in a model. Pick & choose what you like, and please let me know if there are any errors or new/changed features out there. Reddit comment thread: http…
# 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
@sjaveed

This comment has been minimized.

Show comment
Hide comment
@sjaveed

sjaveed Nov 8, 2013

This is a pretty sweet template for how to organize your model tests. Thanks!

sjaveed commented Nov 8, 2013

This is a pretty sweet template for how to organize your model tests. Thanks!

@kyletcarlson

This comment has been minimized.

Show comment
Hide comment
@kyletcarlson

kyletcarlson Nov 15, 2013

@sjaveed Thanks! I had to write several hundred tests for a project at work & made this to make my job easier. Remember: a lazy developer is a good developer!

Owner

kyletcarlson commented Nov 15, 2013

@sjaveed Thanks! I had to write several hundred tests for a project at work & made this to make my job easier. Remember: a lazy developer is a good developer!

@andrewmartin

This comment has been minimized.

Show comment
Hide comment
@andrewmartin

andrewmartin Feb 25, 2014

This is awesome. I'm really green to testing and the expectations always through me off. Killer to see these in one place! Out of curiosity, do you have any open source projects you have up that use a good amount of these tests? Would love to kind of play with a real app. Either way, thank you for sharing this!

This is awesome. I'm really green to testing and the expectations always through me off. Killer to see these in one place! Out of curiosity, do you have any open source projects you have up that use a good amount of these tests? Would love to kind of play with a real app. Either way, thank you for sharing this!

@kyletcarlson

This comment has been minimized.

Show comment
Hide comment
@kyletcarlson

kyletcarlson Mar 22, 2014

Thanks @andrewmartin! I wrote a ton of tests for my company and noticed i was doing the same thing over and over. This made it sooo much easier.

Owner

kyletcarlson commented Mar 22, 2014

Thanks @andrewmartin! I wrote a ton of tests for my company and noticed i was doing the same thing over and over. This made it sooo much easier.

@egeersoz

This comment has been minimized.

Show comment
Hide comment
@egeersoz

egeersoz Jun 29, 2014

This is amazing. Do you have a similar template for testing controllers?

This is amazing. Do you have a similar template for testing controllers?

@kyletcarlson

This comment has been minimized.

Show comment
Hide comment
@kyletcarlson

kyletcarlson Sep 23, 2014

@egeersoz - Nope, sorry. Never test my controllers :)

Owner

kyletcarlson commented Sep 23, 2014

@egeersoz - Nope, sorry. Never test my controllers :)

@fantgeass

This comment has been minimized.

Show comment
Hide comment
@fantgeass

fantgeass Nov 5, 2014

For validating uniqueness we need data in database which we will test uniqueness against.
Example:

context 'when customer already has active order' do
  let(:active_order) { FactoryGirl.create(:order, active: true) }

  it 'disallows several active orders for same customer' do
    new_active_order = FactoryGirl.build(:order, active: true, customer: active_order.customer)

    expect(new_active_order).to validate_uniqueness_of(:customer).scoped_to(:active)
                   .with_message("one customer can't have two active orders")
  end
end

For validating uniqueness we need data in database which we will test uniqueness against.
Example:

context 'when customer already has active order' do
  let(:active_order) { FactoryGirl.create(:order, active: true) }

  it 'disallows several active orders for same customer' do
    new_active_order = FactoryGirl.build(:order, active: true, customer: active_order.customer)

    expect(new_active_order).to validate_uniqueness_of(:customer).scoped_to(:active)
                   .with_message("one customer can't have two active orders")
  end
end
@udit7590

This comment has been minimized.

Show comment
Hide comment
@udit7590

udit7590 Jan 8, 2015

Can you share same kind of cheatsheet for controllers... :)

udit7590 commented Jan 8, 2015

Can you share same kind of cheatsheet for controllers... :)

@nicogaldamez

This comment has been minimized.

Show comment
Hide comment
@nicogaldamez

nicogaldamez Jan 14, 2015

This is great!
+1 for a cheatsheet for controllers, requests and views

This is great!
+1 for a cheatsheet for controllers, requests and views

@eliotsykes

This comment has been minimized.

Show comment
Hide comment

As requested by @udit7590 and @nicogadamez rspec_cheatsheet_controller_spec.rb

@enriquesalceda

This comment has been minimized.

Show comment
Hide comment

Beautiful!

@dsklopp

This comment has been minimized.

Show comment
Hide comment
@dsklopp

dsklopp Aug 21, 2015

+1

I'm struggling through Rails testing with an ocean of articles. Of all I've seen, this gist is the most useful. Thanks!

dsklopp commented Aug 21, 2015

+1

I'm struggling through Rails testing with an ocean of articles. Of all I've seen, this gist is the most useful. Thanks!

@gurgelrenan

This comment has been minimized.

Show comment
Hide comment

Same felling @dsklopp

@avifoxi

This comment has been minimized.

Show comment
Hide comment

avifoxi commented Oct 2, 2015

AWESOME

@ParinVachhani

This comment has been minimized.

Show comment
Hide comment
@ParinVachhani

ParinVachhani Oct 11, 2015

Thanks for sharing this! It's a very helpful template for newbies like me!

Thanks for sharing this! It's a very helpful template for newbies like me!

@tcaddy

This comment has been minimized.

Show comment
Hide comment
@tcaddy

tcaddy Oct 23, 2015

FYI: a few lines are missing do at the end. Lines: 145, 149, 164

tcaddy commented Oct 23, 2015

FYI: a few lines are missing do at the end. Lines: 145, 149, 164

@myf9000

This comment has been minimized.

Show comment
Hide comment
@myf9000

myf9000 Jan 17, 2016

Thanks a lot! This approach for test RSpec is for me the best! Probably I'm leazy :)

myf9000 commented Jan 17, 2016

Thanks a lot! This approach for test RSpec is for me the best! Probably I'm leazy :)

@rastating

This comment has been minimized.

Show comment
Hide comment
@rastating

rastating Mar 9, 2016

Brilliant! Thanks a lot :)

Brilliant! Thanks a lot :)

@angelfan

This comment has been minimized.

Show comment
Hide comment

👍

@takuma-saito

This comment has been minimized.

Show comment
Hide comment

great 👍

@asad-ali-bhatti

This comment has been minimized.

Show comment
Hide comment
@asad-ali-bhatti

asad-ali-bhatti Aug 16, 2016

You are an angel 👍

You are an angel 👍

@mculp

This comment has been minimized.

Show comment
Hide comment
@mculp

mculp Nov 28, 2016

how do I send you money tips for this? this is really, really good.

mculp commented Nov 28, 2016

how do I send you money tips for this? this is really, really good.

@Arunk1390

This comment has been minimized.

Show comment
Hide comment
@Arunk1390

Arunk1390 Jan 12, 2017

This is great, really helped me out 👍

Arunk1390 commented Jan 12, 2017

This is great, really helped me out 👍

@joshmn

This comment has been minimized.

Show comment
Hide comment
@joshmn

joshmn Feb 3, 2017

i love you.

joshmn commented Feb 3, 2017

i love you.

@msdundar

This comment has been minimized.

Show comment
Hide comment
@msdundar

msdundar Feb 19, 2017

Now matchers, which starts with ensure (ensure_inclusion_of, ensure_length_of) are renamed to validate_inclusion_of, validate_length_of.

thoughtbot/shoulda-matchers@55c8d09

Now matchers, which starts with ensure (ensure_inclusion_of, ensure_length_of) are renamed to validate_inclusion_of, validate_length_of.

thoughtbot/shoulda-matchers@55c8d09

@joecairns

This comment has been minimized.

Show comment
Hide comment
@joecairns

joecairns May 31, 2017

This is really good stuff, thanks for posting it up.

This is really good stuff, thanks for posting it up.

@dbarria

This comment has been minimized.

Show comment
Hide comment
@dbarria

dbarria Aug 29, 2017

Awesome! Thank you!

dbarria commented Aug 29, 2017

Awesome! Thank you!

@Yassir4

This comment has been minimized.

Show comment
Hide comment
@Yassir4

Yassir4 Apr 18, 2018

Awesome! Thanks for sharing
ensure_length_of are renamed to -> validate_length_of

Yassir4 commented Apr 18, 2018

Awesome! Thanks for sharing
ensure_length_of are renamed to -> validate_length_of

@zerokol

This comment has been minimized.

Show comment
Hide comment
@zerokol

zerokol Jun 7, 2018

Really nice!!! =)

zerokol commented Jun 7, 2018

Really nice!!! =)

@renlesterdg

This comment has been minimized.

Show comment
Hide comment
@renlesterdg

renlesterdg Jun 12, 2018

this is awesome!

this is awesome!

@frizbee

This comment has been minimized.

Show comment
Hide comment
@frizbee

frizbee Jun 18, 2018

Please update!
ensure_length_of are renamed to -> validate_length_of

frizbee commented Jun 18, 2018

Please update!
ensure_length_of are renamed to -> validate_length_of

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment