# RSpec Examples (RSpec Book, The Rails 3 Way, Custom) # http://eggsonbread.com/2010/03/28/my-rspec-best-practices-and-tips/ ################################################################################ # command line # rspec --help (show help) # rake -T spec (list all rspec-rails rake tasks) # rspec spec (run all specs in spec folder) # rspec spec/models/user_spec.rb (run only user_spec) # rake spec:models (rails: run all model specs) ################################################################################ # configuration # global before/after/around hooks # (:each, :all, :suite) RSpec.configure do |config| config.after :suite do Mongoid.master.collections.select do |collection| collection.name !~ /system/ end.each(&:drop) end end # inclusion filter RSpec.configure do |c| c.filter = { :focus => true } end describe "group" do it "example 1", :focus => true do end end # exclusion filter RSpec.configure do |c| c.exclusion_filter = { :slow => true } end describe "group", :slow => true do it "example 1" do end it "example 2" do end end # lambda filter require 'ping' RSpec.configure do |c| c.exclusion_filter = { :if => lambda { |what| case what when :network_available !Ping.pingecho "example.com", 10, 80 end } } end describe "network group" do it "example 1", :if => :network_available do end it "example 2" do end end ################################################################################ # describe/context (describe for things, context (alias) for context) describe User do context "#create" do end end ################################################################################ # pending (instead of adding comment to disable example) describe User do before { pending } it "should be successful" do end end describe "an empty array" do it "should be empty" do pending("bug report 18976") do [].should be_empty end end end ################################################################################ # before, after, around (before(:each) executed before each example) describe Stack do context "when empty" do before(:each) do @stack = Stack.new end end end ################################################################################ # helper methods describe Thing do def create_thing(options) thing = Thing.new thing.set_status(options[:status]) thing end end describe Thing do 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 end ################################################################################ # helper modules module UserExampleHelpers def create_valid_user User.new(:email => 'email@example.com', :password => 'shhhhh') end end describe User do include UserExampleHelpers it "does something when it is valid" do user = create_valid_user end end # or include in all example groups RSpec.configure do |config| config.include(UserExampleHelpers) end ################################################################################ # shared examples # spec/support/shared_examples.rb shared_examples_for "a phone field" do it "has 10 digits" do Business.new(phone_field => '8004567890').should have(:no).errors_on(phone_field) end end describe "phone" do let(:phone_field) { :phone } it_should_behave_like "a phone field" end ################################################################################ # let (instead of instance variables in before block) let(:blog_post) { BlogPost.create :title => 'Hello' } let!(:comment) { blog_post.comments.create :text => 'first post' } ################################################################################ # specify (generated descriptions) # instead of repeating expectation with: it "should not be published"...) specify { blog_post.should_not be_published } ################################################################################ # subject (delegate should/should_not to subject) subject { BlogPost.new :title => 'foo', :body => 'bar' } it "sets published timestamp" do subject.publish! subject.should be_published end it { should be_valid } its(:errors) { should be_empty } its(:title) { should == 'foo' } its(:body) { should == 'bar' } its(:published_on) { should == Date.today } ################################################################################ # matchers # be target.should be target.should be_true target.should be_false target.should be_nil target.should_not be_nil thing.should be # passes if thing is not nil or false collection.should be_empty # passes if target.empty? target.should_not be_empty # passes unless target.empty? target.should_not be_under_age(16) # passes unless target.under_age?(16) user.should be_in_role("admin") # passes if user.in_role?("admin") returns true result.should be_close(5.25, 0.005) # be_a, be_an "a string".should be_an_instance_of(String) 3.should be_a_kind_of(Fixnum) 3.should be_a_kind_of(Numeric) 3.should be_an_instance_of(Fixnum) 3.should_not be_instance_of(Numeric) #fails # operators result.should == 3 result.should =~ /some regexp/ result.should be < 7 result.should be <= 7 result.should be >= 7 result.should be > 7 # include [1, 2, 3].should include(1) [1, 2, 3].should_not include(4) "foobar".should include("bar") "foobar".should_not include("baz") # have {:foo => "foo"}.should have_key(:foo) {:bar => "bar"}.should_not have_key(:foo) "this string".should have(11).characters [1, 2, 3].should have(3).items schedule.should have(3).days # passes if schedule.days.length == 3 home_team.should have(9).players_on(field) # delegates players_on to home_team widget.should have(1).error_on(:name) model.should have(:no).errors_on(:title) model.should have(1).record day.should have_exactly(24).hours dozen_bagels.should have_at_least(12).bagels internet.should have_at_most(2037).killer_social_networking_apps # respond_to list.should respond_to(:length) # errors lambda { Object.new.explode! }.should raise_error(NameError) # when all else fails... actual.should satisfy { |actual| block } # passes if block returns true ################################################################################ # expect # for changes expect { BlogPost.create :title => "Hello"}.to change { BlogPost.count }.by(1) expect { seller.accept Offer.new(250_000) }.to change{agent.commission}.by(7_500) expect { blog_post.publish! }.to change { blog_post.published_on }.from(nil).to(Date.today) expect { subject.update_with_omniauth(omniauth) }.to_not change { subject.score } # for errors expect { do_something_risky }.to raise_error expect { account.withdraw 75, :dollars }.to raise_error(/attempted to withdraw 75 dollars/) expect { account.withdraw 75, :dollars }.to raise_error(InsufficientFundsError) course = Course.new(:seats => 20) 20.times { course.register Student.new } lambda { course.register Student.new }.should throw_symbol(:course_full, 20) ################################################################################ # custom matcher dsl # joe.reports_to?(beatrice).should be_true joe.should report_to(beatrice) RSpec::Matchers.define :report_to do |boss| match do |employee| employee.reports_to?(boss) end end ################################################################################ # doubles/stubs/mocks (aliases) # doubles/stubs: method stubs (stub(:method).and_return(data)) # mocks: expectations (should_receive...) describe Statement do it "uses the customer's name in the header" do customer = double('customer') customer.stub(:name).and_return('Aslak') statement = Statement.new(customer) statement.generate.should =~ /^Statement for Aslak/ end end # shortcut customer = double('customer', :name => 'Aslak') # null object null_object = mock('null', :null_object => true) # fakes ages = double('ages') ages.stub(:age_for) do |what| if what == 'drinking' 21 elsif what == 'voting' 18 end end # stub chains article = double() Article.stub_chain(:recent, :published, :authored_by).and_return(article) # partial stubbing/mocking describe WidgetsController do describe "PUT update with valid attributes" do # partial stubbing it "redirects to the list of widgets" do widget = Widget.new() Widget.stub(:find).and_return(widget) widget.stub(:update_attributes).and_return(true) put :update, :id => 37 response.should redirect_to(widgets_path) end # partial mocking it "updates the widget's attributes" do widget = Widget.new() Widget.stub(:find).and_return(widget) widget.should_receive(:update_attributes).and_return(true) put :update, :id => 37 end end end # expectations echo = mock('echo') echo.should_receive(:sound).with("hey").and_return("hey") echo.should_receive(:sound).with("hey") { "hey" } mock_account.should_receive(:withdraw).exactly(1).times network_double.should_receive(:open_connection).at_most(5).times network_double.should_receive(:open_connection).at_least(2).times account_double.should_receive(:withdraw).once account_double.should_receive(:deposit).twice checking_account.should_receive(:transfer).with(50, savings_account) source_account.should_receive(:transfer).with(target_account, instance_of(Fixnum)) source_account.should_receive(:transfer).with(anything(), 50) source_account.should_receive(:transfer).with(any_args()) collaborator.should_receive(:message).with(no_args()) mock_account.should_receive(:add_payment_accounts).with(hash_including('Electric' => '123', 'Gas' => '234')) mock_account.should_receive(:add_payment_accounts).with(hash_not_including('Electric' => '123', 'Gas' => '234')) mock_atm.should_receive(:login).with(/.* User/) network_double.should_not_receive(:open_connection) network_double.should_receive(:open_connection).never # ordering (ensure count is called before add) database.should_receive(:count).with('Roster', :course_id => 37).ordered database.should_receive(:add).with(student).ordered # errors account_double.should_receive(:withdraw).and_raise account_double.should_receive(:withdraw).and_raise(InsufficientFunds) account_double.should_receive(:withdraw).and_throw(:insufficient_funds) # custom argument matchers class GreaterThanMatcher def initialize(expected) @expected = expected end def description "a number greater than #{@expected}" end def ==(actual) actual > @expected end end def greater_than(floor) GreaterThanMatcher.new(floor) end calculator.should_receive(:add).with(greater_than(37))