Skip to content

Instantly share code, notes, and snippets.

@ahmadhasankhan
Last active September 22, 2020 12:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ahmadhasankhan/cb0752e09827c3d24bb3d44cc12464b5 to your computer and use it in GitHub Desktop.
Save ahmadhasankhan/cb0752e09827c3d24bb3d44cc12464b5 to your computer and use it in GitHub Desktop.
RSpecCheatSheet
describe 'Expectation Matchers' do
describe 'equivalence matchers' do
it 'will match loose equality with #eq' do
a = "2 cats"
b = "2 cats"
expect(a).to eq(b)
expect(a).to be == b # synonym for #eq
c = 17
d = 17.0
expect(c).to eq(d) # different types, but "close enough"
end
it 'will match value equality with #eql' do
a = "2 cats"
b = "2 cats"
expect(a).to eql(b) # just a little stricter
c = 17
d = 17.0
expect(c).not_to eql(d) # not the same, close doesn't count
end
it 'will match identity equality with #equal' do
a = "2 cats"
b = "2 cats"
expect(a).not_to equal(b) # same value, but different object
c = b
expect(b).to equal(c) # same object
expect(b).to be(c) # synonym for #equal
end
end
describe 'truthiness matchers' do
it 'will match true/false' do
expect(1 < 2).to be(true) # do not use 'be_true'
expect(1 > 2).to be(false) # do not use 'be_false'
expect('foo').not_to be(true) # the string is not exactly true
expect(nil).not_to be(false) # nil is not exactly false
expect(0).not_to be(false) # 0 is not exactly false
end
it 'will match truthy/falsey' do
expect(1 < 2).to be_truthy
expect(1 > 2).to be_falsey
expect('foo').to be_truthy # any value counts as true
expect(nil).to be_falsey # nil counts as false
expect(0).not_to be_falsey # but 0 is still not falsey enough
end
it 'will match nil' do
expect(nil).to be_nil
expect(nil).to be(nil) # either way works
expect(false).not_to be_nil # nil only, just like #nil?
expect(0).not_to be_nil # nil only, just like #nil?
end
end
describe 'numeric comparison matchers' do
it 'will match less than/greater than' do
expect(10).to be > 9
expect(10).to be >= 10
expect(10).to be <= 10
expect(9).to be < 10
end
it 'will match numeric ranges' do
expect(10).to be_between(5, 10).inclusive
expect(10).not_to be_between(5, 10).exclusive
expect(10).to be_within(1).of(11)
expect(5..10).to cover(9)
end
end
describe 'collection matchers' do
it 'will match arrays' do
array = [1,2,3]
expect(array).to include(3)
expect(array).to include(1,3)
expect(array).to start_with(1)
expect(array).to end_with(3)
expect(array).to match_array([3,2,1])
expect(array).not_to match_array([1,2])
expect(array).to contain_exactly(3,2,1) # similar to match_array
expect(array).not_to contain_exactly(1,2) # but use individual args
end
it 'will match strings' do
string = 'some string'
expect(string).to include('ring')
expect(string).to include('so', 'ring')
expect(string).to start_with('so')
expect(string).to end_with('ring')
end
it 'will match hashes' do
hash = {:a => 1, :b => 2, :c => 3}
expect(hash).to include(:a)
expect(hash).to include(:a => 1)
expect(hash).to include(:a => 1, :c => 3)
expect(hash).to include({:a => 1, :c => 3})
expect(hash).not_to include({'a' => 1, 'c' => 3})
end
end
describe 'other useful matchers' do
it 'will match strings with a regex' do
# This matcher is a good way to "spot check" strings
string = 'The order has been received.'
expect(string).to match(/order(.+)received/)
expect('123').to match(/\d{3}/)
expect(123).not_to match(/\d{3}/) # only works with strings
email = 'someone@somewhere.com'
expect(email).to match(/\A\w+@\w+\.\w{3}\Z/)
end
it 'will match object types' do
expect('test').to be_instance_of(String)
expect('test').to be_an_instance_of(String) # alias of #be_instance_of
expect('test').to be_kind_of(String)
expect('test').to be_a_kind_of(String) # alias of #be_kind_of
expect('test').to be_a(String) # alias of #be_kind_of
expect([1,2,3]).to be_an(Array) # alias of #be_kind_of
end
it 'will match objects with #respond_to' do
string = 'test'
expect(string).to respond_to(:length)
expect(string).not_to respond_to(:sort)
end
it 'will match class instances with #have_attributes' do
class Car
attr_accessor :make, :year, :color
end
car = Car.new
car.make = 'Dodge'
car.year = 2010
car.color = 'green'
expect(car).to have_attributes(:color => 'green')
expect(car).to have_attributes(
:make => 'Dodge', :year => 2010, :color => 'green'
)
end
it 'will match anything with #satisfy' do
# This is the most flexible matcher
expect(10).to satisfy do |value|
(value >= 5) && (value <=10) && (value % 2 == 0)
end
end
end
describe 'predicate matchers' do
it 'will match be_* to custom methods ending in ?' do
# drops "be_", adds "?" to end, calls method on object
# Can use these when methods end in "?", require no arguments,
# and return true/false.
# with built-in methods
expect([]).to be_empty # [].empty?
expect(1).to be_integer # 1.integer?
expect(0).to be_zero # 0.zero?
expect(1).to be_nonzero # 1.nonzero?
expect(1).to be_odd # 1.odd?
expect(2).to be_even # 1.even?
# be_nil is actually an example of this too
# with custom methods
class Product
def visible?; true; end
end
product = Product.new
expect(product).to be_visible # product.visible?
expect(product.visible?).to be true # exactly the same as this
end
it 'will match have_* to custom methods like has_*?' do
# changes "have_" to "has_", adds "?" to end, calls method on object
# Can use these when methods start with "has_", end in "?",
# and return true/false. Can have arguments, but not required.
# with built-in methods
hash = {:a => 1, :b => 2}
expect(hash).to have_key(:a) # hash.has_key?
expect(hash).to have_value(2) # hash.has_value?
# with custom methods
class Customer
def has_pending_order?; true; end
end
customer = Customer.new
expect(customer).to have_pending_order # customer.has_pending_order?
expect(customer.has_pending_order?).to be true # same as this
end
end
describe 'observation matchers' do
# Note that all of these use "expect {}", not "expect()".
# It is a special block format that allows a
# process to take place inside of the expectation.
it 'will match when events change object attributes' do
# calls the test before the block,
# then again after the block
array = []
expect { array << 1 }.to change(array, :empty?).from(true).to(false)
class WebsiteHits
attr_accessor :count
def initialize; @count = 0; end
def increment; @count += 1; end
end
hits = WebsiteHits.new
expect { hits.increment }.to change(hits, :count).from(0).to(1)
end
it 'will match when events change any values' do
# calls the test before the block,
# then again after the block
# notice the "{}" after "change",
# can be used on simple variables
x = 10
expect { x += 1 }.to change {x}.from(10).to(11)
expect { x += 1 }.to change {x}.by(1)
expect { x += 1 }.to change {x}.by_at_least(1)
expect { x += 1 }.to change {x}.by_at_most(1)
# notice the "{}" after "change",
# can contain any block of code
z = 11
expect { z += 1 }.to change { z % 3 }.from(2).to(0)
# Must have a value before the block
# Must change the value inside the block
end
it 'will match when errors are raised' do
# observes any errors raised by the block
expect { raise StandardError }.to raise_error
expect { raise StandardError }.to raise_exception
expect { 1 / 0 }.to raise_error(ZeroDivisionError)
expect { 1 / 0 }.to raise_error.with_message("divided by 0")
expect { 1 / 0 }.to raise_error.with_message(/divided/)
# Note that the negative form does
# not accept arguments
expect { 1 / 1 }.not_to raise_error
end
it 'will match when output is generated' do
# observes output sent to $stdout or $stderr
expect { print('hello') }.to output.to_stdout
expect { print('hello') }.to output('hello').to_stdout
expect { print('hello') }.to output(/ll/).to_stdout
expect { warn('problem') }.to output(/problem/).to_stderr
end
end
describe 'compound expectations' do
it 'will match using: and, or, &, |' do
expect([1,2,3,4]).to start_with(1).and end_with(4)
expect([1,2,3,4]).to start_with(1) & include(2)
expect(10 * 10).to be_odd.or be > 50
array = ['hello', 'goodbye'].shuffle
expect(array.first).to eq("hello") | eq("goodbye")
end
end
describe 'composing matchers' do
# some matchers accept matchers as arguments. (new in rspec3)
it 'will match all collection elements using a matcher' do
array = [1,2,3]
expect(array).to all( be < 5 )
end
it 'will match by sending matchers as arguments to matchers' do
string = "hello"
expect { string = "goodbye" }.to change { string }.
from( match(/ll/) ).to( match(/oo/) )
hash = {:a => 1, :b => 2, :c => 3}
expect(hash).to include(:a => be_odd, :b => be_even, :c => be_odd)
expect(hash).to include(:a => be > 0, :b => be_within(2).of(4))
end
it 'will match using noun-phrase aliases for matchers' do
# These are built-in aliases that make
# specs read better by using noun-based
# phrases instead of verb-based phrases.
# valid but awkward example
fruits = ['apple', 'banana', 'cherry']
expect(fruits).to start_with( start_with('a') ) &
include( match(/a.a.a/) ) &
end_with( end_with('y') )
# improved version of the previous example
# "start_with" becomes "a_string_starting_with"
# "end_with" becomes "a_string_ending_with"
# "match" becomes "a_string_matching"
fruits = ['apple', 'banana', 'cherry']
expect(fruits).to start_with( a_string_starting_with('a') ) &
include( a_string_matching(/a.a.a/) ) &
end_with( a_string_ending_with('y') )
# valid but awkward example
array = [1,2,3,4]
expect(array).to start_with( be <= 2 ) |
end_with( be_within(1).of(5) )
# improved version of the previous example
# "be <= 2" becomes "a_value <= 2"
# "be_within" becomes "a_value_within"
array = [1,2,3,4]
expect(array).to start_with( a_value <= 2 ) |
end_with( a_value_within(1).of(5) )
end
end
end
require 'rails_helper'
RSpec.describe TodosController, :type => :controller do
describe "GET #index" do
#describe "POST #create" do
#describe "GET #show" do
#describe "PATCH #update" do (or PUT #update)
#describe "DELETE #destroy" do
#describe "GET #new" do
#describe "GET #edit" do
# NORMALLY, you DO NOT want render_views, or you only want to call it in
# a single context.
# More on render_views:
# https://www.relishapp.com/rspec/rspec-rails/v/3-1/docs/controller-specs/render-views
render_views # ONLY have this if you're certain you need it
it "reads like a sentence (almost)" do
# Available HTTP methods: post, get, patch, put, delete, head
get :index
params = { id: 123 }
get :edit, params # old non-kwarg style
get :edit, params: params # new kwarg style
params = { widget: { description: 'Hello World' } }
params.merge!(format: :js) # Specify format for AJAX/JS responses (e.g. create.js.erb view)
post :create, params # old non-kwarg style
post :create, params: params # new kwarg style
# All optional kwargs:
post :create,
params: {}, # hash with HTTP parameters, may be nil
body: "...", # request body string, appropriately encoded (application/x-www-form-urlencoded or multipart/form-data)
session: {}, # hash of parameters to store in session, may be nil.
flash: {}, # hash of parameters to store in flash, may be nil.
format: :json, # Request format (string or symbol), defaults to nil.
as: :json # Content type must be symbol that corresponds to a mime type, defaults to nil.
# Testing 404s in controllers (assuming default Rails handling of RecordNotFound)
expect { delete :destroy, { id: 'unknown' } }.to raise_error(ActiveRecord::RecordNotFound)
# Rails `:symbolized` status codes at end of each status code page at http://httpstatus.es/
expect(response).to have_http_status(:success) # 200
expect(response).to have_http_status(:forbidden) # 403
expect(response).to redirect_to foo_path
expect(response).to render_template(:template_filename_without_extension)
expect(response).to render_template(:destroy)
# Need response.body? Requires render_views call outside "it" block (see above & read given URL)
expect(response.body).to match /Bestsellers/
expect(response.body).to include "Bestsellers"
expect(response.headers["Content-Type"]).to eq "text/html; charset=utf-8"
expect(response.headers["Content-Type"]).to eq "text/javascript; charset=utf-8"
# assigns(:foobar) accesses the @foobar instance variable
# the controller method made available to the view
# Think of assigns(:widgets) as @widgets in the controller method
expect(assigns(:widgets)).to eq([widget1, widget2, widget3])
# Think of assigns(:product) as @product in the controller method
expect(assigns(:product)).to eq(bestseller)
expect(assigns(:cat)).to be_cool # cat.cool is a boolean, google "rspec predicate matchers"
expect(assigns(:employee)).to be_a_new(Employee)
# Asserting flash messages
expect(flash[:notice]).to eq "Congratulations on buying our stuff!"
expect(flash[:error]).to eq "Buying our stuff failed :-("
expect(flash[:alert]).to eq "You didn't buy any of our stuff!!!"
# Query the db to assert changes persisted
expect(Invoice.count).to eq(1)
# Reload from db an object fetched in test setup when its record in db
# is updated by controller method, otherwise you're testing stale data
employee.reload
invoice.reload
product.reload
widget.reload
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment