The RSpec Toolkit
David Chelimsky
7 Sept 2012
Windy City Rails
David Chelimsky
7 Sept 2012
Windy City Rails
group = describe Something do
end
pp group.metadata
# {:example_group=>
# {:description_args=>[Something],
# :caller=> [ ... ]}}
example = nil
describe Something do
example = it "does something" do
end
end
pp example.metadata
# { :example_group => { ... },
# :description_args => ["does something"],
# :caller=>[ ... ]
# }
describe Something do
it "does something" do
# ...
end
end
example.metadata[:file_path] # path/to/something_spec.rb
example.metadata[:line_number] # 2
example.metadata[:location] # path/to/something_spec.rb:2
example.metadata[:description] # does something
example.metadata[:full_description] # Something does something
# in any formatter: def start(example_count); end def example_group_started(example_group); end def example_group_finished(example_group); end def example_started(example); end def example_passed(example); end def example_pending(example); end def example_failed(example); end
--format progress --format documentation --format html --format textmate --format json # coming in rspec-2.12
def example_passed(example) puts example.metadata[:description] end
def dump_commands_to_rerun_failed_examples
puts "rspec #{example.metadata[:location]}")
end
example.metadata[:execution_result][:run_time]
--require rspec/instafail --format RSpec::Instafail --format Fuubar --format NyanCatMusicFormatter
RSpec.configure do |config|
config.run_all_when_everything_filtered = true
config.filter_run :focus => true
end
describe Account do
it "accepts deposits", :focus => true do
end
it "prevents overdraft" do
end
end
# { :example_group => { ... },
# :description_args => ["accepts deposits"],
# :focus => true
# }
RSpec.configure do |config|
config.run_all_when_everything_filtered = true
config.filter_run :focus => true
end
describe Account, :focus => true do
it "accepts deposits" do
end
it "prevents overdraft" do
end
end
# { :example_group => {
# :description_args => [Account],
# :focus => true
# }}
RSpec.configure do |config|
config.run_all_when_everything_filtered = true
config.filter_run_excluding :slow => true
end
describe Account do
it "accepts deposits", :slow => true do
end
it "prevents overdraft" do
end
end
# { :example_group => { ... },
# :description_args => ["accepts deposits"],
# :slow => true
# }
RSpec.configure do |config|
config.filter_run_including :iteration => 1
end
describe Account do
it "accepts deposits", :iteration => 1 do
end
it "prevents overdraft" do
end
end
# { :example_group => { ... },
# :description_args => ["accepts deposits"],
# :iteration => 1
# }
RSpec.configure do |config|
# this will be the default in rspec-3.0
config.treat_symbols_as_metadata_keys_with_true_values = true
end
describe Account do
it "accepts deposits", :foo do
end
it "prevents overdraft" do
end
end
# { :example_group => { ... },
# :description_args => ["accepts deposits"],
# :foo => true
# }
describe Ruby19OnlyFeature, :if => RUBY_VERSION >= "1.9" do # ... end describe NotOn186, :unless => RUBY_VERSION == "1.8.6" do # ... end describe "generate gold master", :if => ENV['GENERATE'] do # ... end
RSpec.configure do |config|
config.before(:each, :load_settings) do
MyApp::Settings.load # expensive, but memoized
end
end
describe "something that needs settings", :load_settings do
# ...
end
describe "something that doesn't need settings" do
# ...
end
# in rspec-rails
RSpec::configure do |c|
def c.escaped_path(*parts)
Regexp.compile(parts.join('[\\\/]'))
end
c.include RSpec::Rails::RequestExampleGroup,
:type => :request,
:example_group => {
:file_path => c.escaped_path(%w[spec (requests|integration)])
}
c.include RSpec::Rails::ControllerExampleGroup,
:type => :controller,
:example_group => {
:file_path => c.escaped_path(%w[spec controllers])
}
end
RSpec.configure do |config| config.include FakeFS::SpecHelpers, :fakefs end describe "something that writes files", :fakefs => true do # ... end
describe Account do
it "accepts deposits" do
end
it "prevents overdraft", :pending => true do
end
end
# { :description_args => ["prevents overdraft"],
# :pending => true
# }
RSpec.configuration do |c| # these two are built-in c.alias_example_to :focus, :focus => true c.alias_example_to :pending, :pending => true end describe Account do # it "accepts deposits", :focus => true do focus "accepts deposits" do end # it "prevents overdraft", :pending => true do pending "prevents overdraft" do end end
RSpec.configuration do |c|
# ... and so is this
c.alias_example_to :xit,
:pending => 'Temporarily disabled with xit'
end
describe Account do
# it "prevents overdraft",
# :pending => 'Temporarily disabled with xit'
xit "prevents overdraft" do
end
it "accepts deposits" do
end
end
RSpec.configuration do |c|
# Add your own:
c.alias_example_to :next,
:pending => 'Next iteration'
end
describe Account do
it "accepts deposits" do
end
next "prevents overdraft" do
end
end
RSpec.configuration do |c|
# attributes
c.default_path = "./behavior"
c.output_stream = File.new("./rspec.out")
c.error_stream = File.new("./rspec.err")
c.fail_fast = true
# will be default in rspec-3.0
c.treat_symbols_as_metadata_keys_with_true_values = true
# methods
c.filter_run :focus # including
c.filter_run_including :focus
c.filter_run_excluding :slow
c.mock_with :mocha
c.expect_with :stdlib
end
# display all command line options
rspec --help
Usage: rspec [options] [files or directories]
-I PATH Specify PATH to add to $LOAD_PATH (may be used more than once).
-r, --require PATH Require a file.
-O, --options PATH Specify the path to a custom options file.
--order TYPE[:SEED] Run examples by the specified order type.
[default] files are ordered based on the underlying file
system's order
[rand] randomize the order of files, groups and examples
[random] alias for rand
[random:SEED] e.g. --order random:123
--seed SEED Equivalent of --order rand:SEED.
# tag is synonymous w/ filter
rspec --tag focus:true # adds to any hard-coded filters
rspec --tag ~slow:true # examples _not_ tagged w/ slow:true
rspec --tag ~slow:true --tag issue:137 # tags are additive
rspec --tag focus
rspec --tag ~slow
rspec --tag ~slow --tag issue:137
--color --order random --format progress --profile
--color
# load current-iteration.opts instead of ./.rspec and ~/.rspec rspec --options current-iteration.opts
# in ./current-iteration.opts --tag iteration:27
or, with --options option
Keep structural configuration (before/after hooks, includes, etc) in RSpec.configuration and keep run-time configuration (filters, order, fail-fast, etc) on the command line and in options files.
# in spec/spec_helper.rb RSpec.configuration do |config| config.include FakeFS::SpecHelpers, :fakefs # exception to this guideline config.run_all_when_everything_filtered = true config.filter_run_including :focus end # in .rspec --color --format documentation
actual.should matcher actual.should matcher, message actual.should_not matcher actual.should_not matcher, message #examples account.balance.should eq Money.new(1_000_000, :USD) account.overdrawn?.should be_false, "expected account not to be overdrawn" list.should_not be_empty list.should_not be_empty, "expected at least one item"
article.comments.should # NoMethodError
# because ActiveRecord::CollectionProxy removes most
# methods (including should and should_not)
instance_methods.each { |m|
undef_method m unless
m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/
}
# since rspec-expectations-2.11.0 RSpec.configure do |c| c.add_should_and_should_not_to ActiveRecord::CollectionProxy end
# optional syntax since rspec-expectations-2.11.0 expect(actual).to matcher expect(actual).to matcher, message expect(actual).not_to matcher expect(actual).not_to matcher, message #examples expect(account.balance).to eq Money.new(1_000_000, :USD) expect(account.overdrawn?).to be_false, "expected account not to be overdrawn" expect(list).not_to be_empty expect(list).not_to be_empty, "expected at least one item"
actual.should == expected | # not supported with expect
actual.should =~ expected | # not supported with expect
actual.should === expected | # not supported with expect
|
actual.should be < expected | expect(actual).to be < expected
actual.should be <= expected | expect(actual).to be <= expected
actual.should be >= expected | expect(actual).to be >= expected
actual.should be > expected | expect(actual).to be > expected
# underlying
lambda { ... }.should matcher
lambda { ... }.should_not matcher
# original "expect" DSL
expect { ... }.to matcher
expect { ... }.not_to matcher
# examples
expect { something }.to raise_exception
expect { something }.not_to change{Widget.count}
describe Article do
it "validates presence of title" do
article = Article.new(:title => nil)
article.valid?.should be_false
article.errors[:title].should include "can't be blank"
end
end
describe Article do
it "validates presence of title" do
article = Article.new(:title => nil)
article.valid?.should be_false
article.errors[:title].should include "can't be blank"
end
it "validates presence of author" do
article = Article.new(:author => nil)
article.valid?.should be_false
article.errors[:author].should include "can't be blank"
end
end
describe Article do
it "validates presence of title" do
described_class.new.should validate_presence_of :title
end
it "validates presence of author" do
described_class.new.should validate_presence_of :author
end
end
describe Article do
it { should validate_presence_of :title }
it { should validate_presence_of :author }
end
# output Article should validate presence of :title should validate presence of :author
describe Article do
it { should validate_presence_of :title } # nail!
it { should validate_presence_of :author } # nail!
it { should have_many :comments } # screw!
end
describe Article do
it { should validate_presence_of :title }
it { should validate_presence_of :author }
it "sorts comments in reverse by default" do
article = Article.create!
comments = [
article.comments.create!,
article.comments.create!
]
article.comments.should eq comments.reverse
end
describe Account do
# good: generic statement about all accounts
it { should validate_presence_of :owner }
# bad: specific statement about a specific account
its(:balance) { should eq Money.new(0, :USD) }
end
# Account
# should validate presence of :owner
# balance
# should eq $0
describe Account do
it { should validate_presence_of :owner }
# slightly less bad given some context
describe "defaults" do
its(:balance) { should eq Money.new(0, :USD) }
end
end
# Account
# should validate presence of :owner
# defaults
# balance
# should eq $0
describe Account do
it { should validate_presence_of :owner }
# better: provides necessary context with no less code
# but in a simpler, more readable form
it "has a default balance of $0" do
Account.new.balance.should eq Money.new(0, :USD)
end
end
# Account
# should validate presence of :owner
# has a default balance of $0
describe Account do
it { should validate_presence_of :owner }
describe "defaults" do
its(:balance) { should eq Money.new(0, :USD) }
end
context "after a deposit" do
subject { Account.new.tap { |a| a.deposit Money.new(25, :USD) } }
its(:balance) { should eq Money.new(25, :USD) }
end
end
# Account
# should validate presence of :owner
# defaults
# balance
# should eq $0
# after a deposit
# balance
# should eq $25
describe Account do
it { should validate_presence_of :owner }
it "has a default balance of $0" do
Account.new.balance.should eq Money.new(0, :USD)
end
it "increases its balance by amount deposited" do
account = Account.new
account.deposit Money.new(25, :USD)
expect(account.balance).to eq Money.new(25, :USD)
end
end
# Account
# should validate presence of :owner
# has a default balance of $0
# increases its balance by amount deposited
describe Person do
describe "#full_name" do
it "concats first_name and last_name" do
person = Person.new :first_name => "Ray",
:last_name => "Hightower"
expect(person.full_name).to eq "Ray Hightower"
end
end
end
describe Person do
describe "#full_name" do
it "concats first_name and last_name" do
person = Person.new :first_name => "Ray",
:last_name => "Hightower"
assert_equal "Ray Hightower", person.full_name
end
end
end
# when you see this: assert_equal "Ray", person.first_name # think this: assert_equal_Ray person.first_name # and you're less likely to do this: assert_equal person.first_name, "Ray" # => Expected: "Joe" # => Actual: "Ray"
$ gem install wrong
require 'wrong/adapters/rspec'
describe Person do
describe "#full_name" do
it "concats first_name and last_name" do
person = Person.new :first_name => "Steve",
:last_name => "Conover"
expect_that { person.full_name == "Steve Conover" }
end
end
end
describe Person do
describe "#full_name" do
it "concats first_name and last_name"
it "ignores a nil first_name"
it "ignores a nil last_name"
end
end
# Person
# #full_name
# concats first_name and last_name
# ignores a nil first_name
# ignores a nil last_name