-
-
Save dnagir/663876 to your computer and use it in GitHub Desktop.
# RSpec 2.0 syntax Cheet Sheet by http://ApproachE.com | |
# defining spec within a module will automatically pick Player::MovieList as a 'subject' (see below) | |
module Player | |
describe MovieList, "with optional description" do | |
it "is pending example, so that you can write ones quickly" | |
it "is already working example that we want to suspend from failing temporarily" do | |
pending("working on another feature that temporarily breaks this one") | |
# actual test code is here, will never be reached | |
end | |
it "is pending when failing" do | |
pending "This will be marked as pending when the block will fail, otherwise (on success) will fail telling 'Why am I pending if I pass?'" do | |
1.should == 2 # will mark example as pending | |
2.should == 2 # will fail asking to remove pending status of example | |
end | |
end | |
# this will automatically generate name of the example based on the expectations inside it ~ 'it' with no description | |
specify { [1,2,3].should have(3).items } | |
#any helper methods, before/after, modules etc declared in the outer group are available in the inner group. | |
describe "outer" do | |
before(:each) { puts "first" } | |
describe "inner" do | |
before(:each) { puts "second" } | |
it { puts "third"} | |
after(:each) { puts "fourth" } | |
end | |
after(:each) { puts "fifth" } | |
end | |
# 'describe' and 'context' are equivalent | |
# I prefer to use 'context' for defining an 'environment' | |
context "when first created" do | |
it "is empty" do | |
movie_list = MovieList.new | |
movie_list.should be_empty | |
end | |
end | |
# I prefer to use 'describe' for nouns, verbs; defining a nested set of specifications | |
describe "forward" do | |
it "should jump to a next movie" do | |
next_movie = MovieList.new(2).forward | |
next_movie.track_number.should == 2 | |
end | |
end | |
end | |
it "will have default subject that corresponds to the instance of first parameter in 'describe'" do | |
subject.class.should be == MovieList | |
end | |
# unless subject is set explicitly | |
subject { MovieList.new(10) } # approximately similar to 'before(:each)' | |
# no need to use 'subject.should', use 'should' | |
specify { should have(10).items } # same as below | |
specify { subject.should have(10).items } | |
# similar to specify { subject.track_number.should == 1} | |
its(:track_number) { should == 1 } | |
context "specs set-up" do | |
# we can run setup before each examle, or all of them | |
before(:each) do | |
@new_on_each_example = YourObject.new | |
end | |
before do | |
@new_on_each_spec_less_verbose = YourObject.new | |
end | |
before(:all) do | |
# Avoid using it as it will bring the 'shared state' into unit tests | |
@same_instance_for_all_examples_within_the_context = YourObject.new | |
end | |
it "can access attributes defined in 'before'" do | |
@new_on_each_example.should_not be_nil | |
@same_instance_for_all_examples_within_the_context.should_not be_nil | |
end | |
# cleanup code can be run the same way using 'after' instead of 'before' | |
# Avoid using 'after' | |
# we can wrap examples: before + after + manual handling | |
# In most cases 'before' + 'after' will work better. | |
around do |example| | |
DB.transaction { example.run } | |
# should handle errors manually, so do not do something like: | |
# DB.start_transaction | |
# example.run | |
# DB.rollback_transaction | |
end | |
it "should run within a transaction" do | |
MovieList.new.save! | |
end | |
let(:new_on_each_example) { ObjectPerExample.new } | |
it "can use method defined by 'let'" do | |
new_on_each_example.should_not be_nil | |
# the object is memoized, so | |
new_on_each_example.should == new_on_each_example | |
end | |
# defining helper methods within context may be more useful than setup | |
def forward(times) do | |
list = MoviewList.new(10) | |
list.forward(times).track_number | |
end | |
it "can use it multiple times" do | |
forward(1).should == 1 | |
forward(2).should == 2 | |
forward(10).should == 1 | |
end | |
# using 'yield' with helper methods | |
def given_thing_with(options) | |
yield Thing.new do |thing| | |
thing.set_status(options[:status]) | |
end | |
end | |
it "should do something when ok" do | |
given_thing_with(:status => 'ok') do |thing| | |
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil) | |
end | |
end | |
# helpers can come from modules | |
module Helpers | |
def shared_help | |
[1,2,3] | |
end | |
end | |
include Helpers | |
it "can use helpers from Module" do | |
shared_help.should == [1,2,3] | |
end | |
# or this module can be included for ALL example groups automatically during configuration: | |
# RSpec.configure do |config| | |
# config.include Helpers | |
# end | |
end | |
context "built-it stubbing, faking, mocking" do | |
it "can stub" do | |
source = double('source') | |
source.stub(:fetch) { [1,2,3,4,5] } | |
source.stub(:fetch_from).and_return([1,2]) # other way | |
MovieList.stub(:find).and_return(MovieList.new) # stub class method | |
implementing = double('source') | |
implementing.stub(:fetch) do |count| | |
count == 5 ? [1,2,3] : [4,5,6,7] # provide stub logic here, easy to use for Fakes | |
end | |
# easily stub chains of calls | |
Blog.stub_chain(:posts, :published, :recent).and_return([1,2,3]) | |
Blog.posts.published.recent.should == [1,2,3] | |
end | |
it "can ignore non-expected method calls (NullObject pattern)" do | |
source = double('source', :url => 'http://example.com').as_null_object | |
source.any_method_call_onwill_return_nil.should be_nil | |
# the source mock object will record the 'any_method_call_onwill_return_nil' message internally though | |
end | |
it "can set expectations" do | |
source = double('source') | |
# arguments | |
source.should_receive(:fetch).with(10, "abc").and_return([1,2]) # expecting arguments (10, "abc") otherwise failing | |
source.should_receive(:fetch).with(instance_of(Integer), "abc").and_return([1,2]) # don't care about 1st argument as long as it is Integer | |
source.should_receive(:fetch).with(10, anything).and_return([1,2]) # don't care about 2nd argument at all | |
source.should_receive(:fetch).with(any_args) # same as not using 'with' - don't care about arguments | |
source.should_receive(:fetch).with(no_args) # 0 arguments, otherwise fail | |
source.should_receive(:fetch).with(hash_including(:count => 10, :url => 'abc')) # arg should be Hash with all the values mentioned | |
source.should_receive(:fetch).with(hash_not_including(:timeout => 5)) # arg should be Hash that contains no ':timout=>5' | |
source.should_receive(:fetch).with(anything, /example/) # 2nd arg shuold match RegEx | |
source.should_receive(:fetch).and_return([1], [1,2], [1,2,3]) # 1st call - [1], 2nd - [1,2], 3rd - [1,2,3], 4th - [1] and so on ... | |
# expectation overrides stub | |
source.stub(:fetch).and_return([1,2]) # will return [1,2] when called | |
source.should_recieve(:fetch).and_return([3,4]) # prev been overriden and will return [3,4] | |
# raising/throwing | |
source.should_receive(:fetch).and_raise # raise Exception | |
source.should_receive(:fetch).and_raise(ZeroDivisionError) # raise ZeroDivisionError | |
source.should_receive(:fetch).and_raise(Exception.new('instance of aexception')) # raise given exception | |
source.should_receive(:fetch).and_throw(:zero) # thro :zero | |
# order | |
source.should_receive(:first).ordered # order matters in relation to others marked as ordered | |
source.should_receive(:dosnt_matter) # don't care about order as long as it is called | |
source.should_receive(:second).ordered # must be called after 'first' | |
# order is not enforced across different objects: | |
double('a').should_receive(:a).ordered # not related to the next one | |
double('b').should_receive(:b).ordered # not related to the prev one | |
# how many times? | |
source.should_recieve(:fetch).exactly(1)times | |
source.should_recieve(:fetch).at_most(5)times | |
source.should_recieve(:fetch).at_least(2)times | |
source.should_recieve(:fetch).twice | |
source.should_recieve(:fetch).once | |
# negative expectations | |
source.should_recieve(:fetch).never | |
source.should_recieve(:fetch).exactly(0)times | |
source.should_not_recieve(:fetch) | |
list = MovieList.new(source) | |
# if source.fetch has not been called, then example will fail | |
end | |
context "custom expectations" do | |
# define custom expection class somewhere | |
class GreaterThanMatcher | |
def initialize(expected) | |
@expected = expected | |
end | |
def description | |
# will generate proper failure message and name of the example | |
"a number greater than #{@expected}" | |
end | |
def ==(actual) | |
# this will be called from | |
actual > @expected | |
end | |
end | |
# add this method to the RSpec (see set-up for global configuration) | |
def greater_than(floor) | |
GreaterThanMatcher.new(floor) | |
end | |
it "can be used in expectations" do | |
subject.should_recieve(:forward).with(greater_than 3) | |
subject.forward(5) | |
end | |
end # custom matchers | |
end # mocking | |
# set of same examples shared accross multiple specs | |
# shared_examples_for should be in a separate file and defined outside of 'describe'/'context' | |
shared_examples_for "any pizza" do | |
it "tastes really good" do | |
@pizza.should taste_really_good | |
end | |
end | |
# to include the shared examples, into example groups: | |
# it will assume @pizza instance variable is available here | |
it_behaves_like "any pizza" | |
context 'defining examples dynamically - everybody knows that :)' do | |
{2 => 4, 3 => 6, 10 => 20}.each do |input, output| | |
it "#{input} * 2 should be equal to #{output}" do | |
(input * 2).should == output | |
# will produce examples: | |
# - 2 * 2 should be equal to 4 | |
# - 3 * 2 should be equal to 6 | |
# - 10 * 2 should be equal to 20 | |
end | |
end | |
end | |
context "matchers" do | |
it "shows built-in matchers" do | |
# TODO: describe ===, eql, equal | |
1.should == 1 | |
1.should_not == 2 # NOT 1.should != 2 | |
1.should_not equal(2) # same as above | |
1.should_not == 2 | |
5.should be > 3 | |
5.should be <= 5 | |
(1.251).should be_close(1.25, 0.005) | |
(1.251).should be_within(0.005).of 1.25 # >= RSpec 2.1 | |
"reg exp".should =~ /exp/ | |
[1,2].should include(1) | |
1.should respond_to(:to_s) | |
true.should be_true | |
0.should be_true | |
"this".should be_true | |
lambda { Object.new.explodde! }.should raise_error(NameError) | |
# nothing fits | |
5.should satisfy { |it| it == 5 } | |
end | |
it "shows cool things" do | |
count = 1 | |
expect { | |
count = 3 | |
}.to change { count }.by(2) | |
expect { | |
# not changing | |
}.to_not change { count } | |
count = 1 | |
expect { | |
count = 3 | |
}.to change { count }.to(3) | |
count = 1 | |
expect { | |
count = 3 | |
}.to change { count }from(1).to(3) | |
# raise-rescue - exception handling | |
expect {2 / 0}.to raise_error("divided by 0") | |
expect {2 / 0}.to raise_error(/by 0/) | |
expect {2 / 0}.to raise_error(ZeroDivisionError) | |
# try-catch - expected circumstance handling | |
lambda { throw :room_is_full }.should throw_symbol(:room_is_full) | |
# predicates | |
nil.should be_nil #call nil.nil? | |
[].should be_empty # calls [].empty? | |
[1,2,3].should_not be_empty # calls [1,2,3].empty | |
# convert anything that begins with have_ to a predicate on the target object beginning with has_ | |
{:id => 1}.has_key?(:id).should == true | |
# can be written as | |
{:id => 1}.should have_key(:id) # calls {:id => 1}.has_key?(:id) | |
# collections | |
obj = {} | |
def obj.numbers | |
[1,2,3,4] | |
end | |
obj.should have(4).numbers # calls obj.numbers.length | |
[1,2,3,4].should have(4).items # 'items' is 'reserved' to say "ensure number of items on the collection" | |
[1,2,3,4].should be_any {|n| n % 2 == 0} # [1,2,3,4].any? {|n| n %% 2 == 0}.should be_true | |
"stringy".should have(7).charaters # same as items, just syntactic sugar | |
[1,2,3,4].should have_exactly(24).items # same as 'have' | |
obj.should have_at_least(3).numbers | |
end | |
end # built-in matchers | |
context "custom matchers" do | |
# TODO: describe multiple ways | |
#define class | |
class SimilarTo | |
# mandatory - link to the object under test | |
def initialize(it) | |
# object under test | |
@it = it | |
end | |
# mandatory - check the positive condition | |
def matches?(that) | |
@that = that # save to use it in messages | |
@that.to_s.downcase.should == @it.to_s.downcase | |
end | |
# optional - opoosite to mathch? | |
def does_not_matche?(that) | |
result = !matches?(that) | |
@that, @it = @it, @that # swap for negative condition or additionally cusomtize messages | |
result # don't forget to return | |
end | |
# mandatory | |
def failure_message_for_should | |
"expected #{@it} to be similar to #{@that}" | |
end | |
# optional | |
def failure_message_for_should_not | |
"expected #{@it} to be different from #{@that}" | |
end | |
#optional | |
def description | |
"#{@it} should be similar to #{@that}" | |
end | |
end | |
#define method on example (see set-up to incude in all examples) | |
def similar_to(that) | |
SimilarTo.new(that) | |
end | |
end # custom matchers | |
context "macros" do | |
module ControllerMacros | |
def should_render(template) | |
it "should render the #{template} template" do | |
do_request | |
response.should render_template(template) | |
end | |
end | |
def should_assign(hash) | |
variable_name = hash.keys.first | |
model, method = hash[variable_name] | |
model_access_method = [model, method].join('.') | |
it "should assign @#{variable_name} => #{model_access_method}" do | |
expected = "the value returned by #{model_access_method}" | |
model.should_receive(method).and_return(expected) | |
do_request | |
assigns[variable_name].should == expected | |
end | |
end | |
def get(action) | |
define_method :do_request do | |
get action | |
end | |
yield | |
end | |
end | |
RSpec.configure do |config| | |
config.use_transactional_fixtures = true | |
config.use_instantiated_fixtures = false | |
config.fixture_path = RAILS_ROOT + '/spec/fixtures/' | |
config.extend(ControllerMacros, :type => :controller) | |
end | |
end # macros | |
end # module |
Nice work! This was really helpful to me tonight.
I'm really glad you found it useful.
Man, awesome cheat sheet, thanks a lot!
nice one! thanks.
How do you differentiate between resource-description tests and context tests? It's not clear to me from this gist. I'm also trying to understand how the community thinks to differentiate between the styles
vs.
Thanks
@NewAlexandria, if you are talking about descrtibe
and context
blocks then I use describe for feature description and context for setting up different states/behaviours within the features.
For example (from top of my head):
describe "registration" do
subject { register }
let(:email) { "abc@example.com" }
def register
User.new(:email => email).tap { |u| u.valid? }
end
it { should be_valid }
context "when user exists" do
before { User.create :email => email }
it { should have_error_on :email }
end
context "with invalid email" do
let(:email) { "invalid" }
it { should be_valid }
end
end
@dnagir thanks but no, I am specifically talking about the difference between using :scope_name do
vs. `"#scope_name" do``
I made a small maitenance, especially about the imcompatibility of `ordered' method.
https://gist.github.com/2213282
Great work! There are some typo. It should be ".times" instead of "times"
Very handy!
object.should have(1).error_on(:attribute)
object.should have(:no).errors_on(:another_attribute)
This is a great resource! However, I was wondering if you had any tips on mocking modules/classes with submodules/subclasses? Specifically, I am attempting to mock the Hallon gem and it has ::Session
and ::Player
that I need to mock as my function receives just the Hallon module wrapper. So my double
needs to have ::Session
and ::Player
so that they can be stubbed like thus:
adapter = double
adapter::Player.should_receive(:new) # or .stub and the expected return
adapter::Session.should_receive(:initialize) # same as above
I'm relatively new to Ruby and rspec so bear with me if I am approaching this issue from the completely wrong direct. Any feedback or ideas?
Time for a 2.14 update! (expect & allow)
Thanks, amazing work!, btw there are some typos It should be "should_receive" instead of "should_recieve" on some places
Thanks man! this is exactly what I was looking for!
Great examples!!