Skip to content

Instantly share code, notes, and snippets.

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:
# Shoulda-matchers:
# shoulda-callback-matchers:
# 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
# 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
# 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("").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
describe "ActiveRecord associations" do
# 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
it { expect(asset).to have_readonly_attribute(:uuid) }
# Databse columns/indexes
it { expect(user).to have_db_column(:political_stance).of_type(:string).with_options(default: 'undecided', null: false)
it { expect(user).to have_db_index(:email).unique(:true)
context "callbacks" do
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) }
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:
expect(Vote.loved.first).to eq(love_vote)
it "has another scope that works" do
expect(model.scope_name(conditions)).to eq(result_expected)
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) }
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)
it "does what it's supposed to..."
expect(factory_instance.method_to_test).to eq(value_you_expect)
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) }
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)
Copy link

avifoxi commented Oct 2, 2015


Copy link

ParinVachhani commented Oct 11, 2015

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

Copy link

tcaddy commented Oct 23, 2015

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

Copy link

myf9000 commented Jan 17, 2016

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

Copy link

rastating commented Mar 9, 2016

Brilliant! Thanks a lot :)

Copy link

angelfan commented Apr 21, 2016


Copy link

takuma-saito commented May 26, 2016

great 👍

Copy link

asad-ali-bhatti commented Aug 16, 2016

You are an angel 👍

Copy link

mculp commented Nov 28, 2016

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

Copy link

ghost commented Jan 12, 2017

This is great, really helped me out 👍

Copy link

joshmn commented Feb 3, 2017

i love you.

Copy link

msdundar commented Feb 19, 2017

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


Copy link

joecairns commented May 31, 2017

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

Copy link

dbarria commented Aug 29, 2017

Awesome! Thank you!

Copy link

Yassir4 commented Apr 18, 2018

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

Copy link

alvesoaj commented Jun 7, 2018

Really nice!!! =)

Copy link

rendegosling commented Jun 12, 2018

this is awesome!

Copy link

frizbee commented Jun 18, 2018

Please update!
ensure_length_of are renamed to -> validate_length_of

Copy link

johncorderox commented Jul 23, 2018

helped so much for my api. thanks m8

Copy link

nrpx commented Aug 30, 2018

Hi! This is awesome! +1 for good job))
A little add: in block with public class methods we must test methods of class, not instance (factory_instance is an instance object, right?).

Copy link

benkoshy commented Oct 8, 2018

Please let me know if there are any errors or new/changed features out there

FactoryGirl gem is now called FactoryBot.

Copy link

dfurutani commented Mar 29, 2019

Awesome job... on line 89 you need to close parenthesis on :profile
it { expect(user).to have_one(:profile) }

Copy link

LaSkilzs commented Apr 5, 2019

Sweet, just learning and this is a great reference..... Thanks!

Copy link

jluczak commented Apr 10, 2019

Thank you!!! <3

Copy link

ivanmatas commented May 7, 2019

Great stuff, thanks!

Copy link

segunadeleye commented Jul 5, 2019

Some interesting conversations going on here about this gist. You might want to check it out.

Copy link

tahsin352 commented Jan 19, 2021

Now matchers, which starts with ensure (ensure_inclusion_in, ensure_length_of) are renamed to validate_inclusion_in, validate_length_of (55c8d09).

Copy link

RobinDaugherty commented Mar 6, 2021

@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 { belong_to(:other_model_object).optional(true) }

or when describing a method on the object:

described ModelObject do
  described '#other_model_object' do
    subject(:other_model_object) { }
    it { eq(other_object) }

This ensures meaningful spec output, since it flows in English, which is the goal of rspec.

Copy link

asyraffff commented Jul 27, 2021

This is cool !!!

Copy link

alexventuraio commented Apr 12, 2022

Something similar but without shoulda-matchers?

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