rspec小抄 rspec
程序员文章站
2024-02-22 19:32:10
...
# 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
推荐阅读
-
rspec小抄 rspec
-
Ruby 行为驱动设计 (RSPEC) 博客分类: Ruby rspecruby
-
organize rspec2 tests into 'unit' (fast) and 'integration' (slow) categories 博客分类: Ruby rubyrspecrake
-
improve rspec speed GC 博客分类: ruby rspecruby
-
rspec in rails 博客分类: rspecrails rspecrails
-
RSpec笔记 - let 和 let! 博客分类: 测试Ruby on Rails RSpecRuby on Rails
-
rspec rspec
-
Ruby on rails 创建基于Rspec项目 博客分类: ROR rspecrailsruby
-
rspec controller测试 博客分类: 【tec】rails测试 renderresponserspec
-
从JUnit到Rspec的尝试 博客分类: rails rspecjunit