rspec小抄


# 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值