-
Star
(465)
You must be signed in to star a gist -
Fork
(187)
You must be signed in to fork a gist
-
-
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) { "[email protected]" }
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 aboveI'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!!