Straight Up RSpec 3a neat Ruby BDD tool
Organization $0
Running Specs $0
Configuration $0
Refactoring Exercise $0
Other Libraries $0
The Menu
Organization
.rspeclib/…spec/spec_helper.rbspec/support/matchers/drink.rbspec/martini_spec.rb
spec_helper.rb
require 'barkeep'!Dir["#{File.dirname(__FILE__)}/support/**/*.rb"]. each {|file| require file }
spec_helper
Running Specs
.rspec
--color--warnings--require spec_helper
.rspec
.rspec-local
—-format documentation--backtrace
.rspec-local
$ rspec spec/martini_spec.rb:15:22
$ rspec --format documentation
$ rspec --fail-fast
$ rspec --tag js:true
$ rspec --profile…!Top 10 slowest examples (0.00226 seconds, 57.2% of total time): Martini with a mixer #ingredients should include "vermouth" 0.00058 seconds ./spec/martini_spec.rb:15 InitializeWithAttrsSpec with attributes #pages should eq 123 0.00052 seconds ./spec/initialize_with_attrs_spec.rb:16…
$ rspec --order random…Randomized with seed 63958
$ rspec --order random:63958
martini_spec.rb
describe "Martini" do … it "should have ingredients", :focus do expect(martini).to respond_to(:ingredients) end …end
:focus
martini_spec.rb
describe "Martini" do … fit "should have ingredients" do expect(martini).to respond_to(:ingredients) end …end
fit
martini_spec.rb
describe "Martini" do … it "should have ingredients", :skip do expect(martini).to respond_to(:ingredients) end …end
:skip
martini_spec.rb
describe "Martini" do … xit "should have ingredients" do expect(martini).to respond_to(:ingredients) end …end
xit
martini_spec.rb
describe "Martini" do … pending "should have price" do expect(@martini).to be_pricey end …end
pending
Configuration
support/configure.rb
RSpec.configure do |config| config.include MyMartiniMatchers config.include ModelHelpers, :type => :modelend!!# spec/model/martini_spec.rb!describe Martini, :type => :model do …end
RSpec.configure
support/configure.rb
RSpec.configure do |c| c.before(:suite) {} # once c.before(:context) {} # once before each group c.before(:example) {} # once before each example! c.after(:example) {} # once after each example c.after(:context) {} # once after each group c.after(:suite) {} # once! # run before each example of type :model config.before(:example, :type => :model) {}end
RSpec.configure
support/configure.rb
RSpec.configure do |c| c.filter_run focus: true c.run_all_when_everything_filtered = trueend!# in any spec filedescribe "thing" do it "does something interesting", :focus do # .... endend
Inclusion
support/configure.rb
RSpec.configure do |c| c.exclusion_filter = { :ruby => lambda { |version| !(RUBY_VERSION.to_s =~ /^#{version.to_s}/) }}end!# in any spec filedescribe "something" do it "does something", :ruby => 1.8 do # .... end it "does something", :ruby => 2.1 do # ....
Exclusion
Barkeepgithub.com/gsterndale/barkeep
an upgrade & refactoring exercise
martini_spec.rb
describe "Barkeep::Martini" do before do @martini = Barkeep::Martini.new end it "should have an attribute named booze" do @martini.should respond_to(:booze) @martini.should respond_to(:booze=) end it "should have an attribute named garnish" do @martini.should respond_to(:garnish) @martini.should respond_to(:garnish=) endend
Original Martini 2.X Spec
martini_spec.rb
describe "Barkeep::Martini" do before do @martini = Barkeep::Martini.new end it "should have an attribute named booze" do expect(@martini).to respond_to(:booze) expect(@martini).to respond_to(:booze=) end it "should have an attribute named garnish" do expect(@martini).to respond_to(:garnish) expect(@martini).to respond_to(:garnish=) endend
Martini Spec 3.0
have_attribute.rb
module AttributeMatchers! Spec::Matchers.define :have_attribute do |name| match do |target| getter = name.to_sym setter = (name.to_s + "=").to_sym target.respond_to?(getter) && target.respond_to?(setter) end end!end
Custom Matcher
martini_spec.rb
describe "Barkeep::Martini" do include AttributeMatchers before do @martini = Barkeep::Martini.new end it "should have an attribute named booze" do expect(@martini).to have_attribute(:booze) end it "should have an attribute named garnish" do expect(@martini).to have_attribute(:garnish) endend
Custom Matcher
martini_spec.rb
describe "Barkeep::Martini" do include AttributeMatchers subject { Barkeep::Martini.new } it "should have an attribute named booze" do expect(subject).to have_attribute(:booze) end it "should have an attribute named garnish" do expect(subject).to have_attribute(:garnish) endend
subject()
martini_spec.rb
describe "Barkeep::Martini" do include AttributeMatchers subject(:martini) { Barkeep::Martini.new } it "should have an attribute named booze" do expect(martini).to have_attribute(:booze) end it "should have an attribute named garnish" do expect(martini).to have_attribute(:garnish) endend
Named subject()
martini_spec.rb
describe "Barkeep::Martini" do include AttributeMatchers subject { Barkeep::Martini.new } it "should have an attribute named booze" do is_expected.to have_attribute(:booze) end it "should have an attribute named garnish" do is_expected.to have_attribute(:garnish) endend
Implicit subject()
martini_spec.rb
describe "Barkeep::Martini" do include AttributeMatchers subject { Barkeep::Martini.new } it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:garnish) }end
DRY Messages
martini_spec.rb
describe Barkeep::Martini do include AttributeMatchers it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:garnish) }end
Derived subject()
have_attribute.rb
module AttributeMatchers …end!RSpec.configure do |config| config.include AttributeMatchersend
config.include()
martini_spec.rb
describe Barkeep::Martini do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:garnish) }end
config.include()
martini_spec.rb
describe "Barkeep::Martini" do before do @martini = Barkeep::Martini.new end it "should have an attribute named booze" do @martini.should respond_to(:booze) @martini.should respond_to(:booze=) end it "should have an attribute named garnish" do @martini.should respond_to(:garnish) @martini.should respond_to(:garnish=) endend
Original Martini 2.X Spec
martini_spec.rb
describe Barkeep::Martini do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:garnish) }end
Refactored Martini 3.0 Spec
whiskey_spec.rb
describe Barkeep::Whiskey do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:glass) }end
New Whiskey Spec
martini_spec.rb
describe Barkeep::Martini do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:glass) } it { is_expected.to have_attribute(:garnish) }end
Additions to Martini Spec
support/examples/drink.rb
shared_examples_for "a drink" do it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:glass) }end
Shared Example Group
whiskey_spec.rb
describe Barkeep::Whiskey do it_behaves_like "a drink"end
Whiskey Spec
whiskey_spec.rb
describe Barkeep::Whiskey do include_examples "a drink"end
Whiskey Spec
martini_spec.rb
describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) }end
Martini Spec
support/examples/drink.rb
shared_examples_for "a drink" do |ingredients| it { is_expected.to have_attribute(:booze) } it { is_expected.to have_attribute(:glass) }! describe "#ingredients" do subject { super().ingredients } it { is_expected.to be_a Array } it { is_expected to include *ingredients } endend
Whiskey & Martini #ingredients
martini_spec.rb
describe Barkeep::Martini do it_behaves_like "a drink", "vodka" it { is_expected.to have_attribute(:garnish) }end
Martini Spec
martini_spec.rb
describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) } it { is_expected.to have_attribute(:mixer) } context "with a mixer" do before do @mixer = 'vermouth' subject.mixer = @mixer end it "should include mixer in ingredients" do expect(subject.ingredients).to include @mixer end end
context()
martini_spec.rb
describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) } it { is_expected.to have_attribute(:mixer) } context "with a mixer" do let(:mixer) { 'vermouth' } before { subject.mixer = mixer }! it "should include mixer in ingredients" do expect(subject.ingredients).to include mixer end endend
let()
martini_spec.rb
describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) } it { is_expected.to have_attribute(:mixer) } context "with a mixer" do let(:mixer) { 'vermouth' } before { subject.mixer = mixer }! its(:ingredients) { is_expected.to \ include mixer } endend
its()
martini_spec.rb
describe Barkeep::Martini do it_behaves_like "a drink" it { is_expected.to have_attribute(:garnish) } it { is_expected.to have_attribute(:mixer) }end!describe Barkeep::Martini, ".new with mixer" do let(:mixer) { 'vermouth' } subject { Barkeep::Martini.new(mixer: mixer) } its(:ingredients) { is_expected.to include mixer }end
its()
martini_spec.rb
describe Barkeep::Martini, ".new with mixer" do let(:mixer) { 'vermouth' } subject { Barkeep::Martini.new(:mixer => mixer) } its(:ingredients) { is_expected.to include mixer } its(:ingredients) { is_expected.to have(1).items }end
have()
martini_spec.rb
describe Barkeep::Martini, ".new with mixer" do let(:mixer) { 'vermouth' } subject { Barkeep::Martini.new(:mixer => mixer) } its(:ingredients) { is_expected.to include mixer } it { is_expected.to have(1).ingredients }end
have()
martini_spec.rb
describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients }! it { is_expected.to include \ 'juice', 'vodka', 'olives', 'vermouth' }! it { is_expected.to contain_exactly \ 'vodka', 'vermouth', 'juice', 'olives' }end
Array Matchers
martini_spec.rb
describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients }! it { is_expected.to start_with 'vodka' } it { is_expected.to end_with 'olives' }end
Array Matchers
martini_spec.rb
describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients }! it { is_expected.to all be_a(String) } it { is_expected.to all start_with(/^\w/) }end
Array Matchers
martini_spec.rb
describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients }! it { is_expected.to all \ be_a(String) & start_with(/^\w/) }end
Compound Matchers
martini_spec.rb
describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients }! it { is_expected.to match [ start_with(/^\w/), match(/olives?/) ] }end
Composable Matchers
martini_spec.rb
describe Barkeep::Martini, ".dirty" do let(:martini) { Barkeep::Martini.dirty } subject { martini.ingredients }! it { is_expected.to match [ a_string_starting_with(/^\w/), a_string_matching(/olives?/) ] }end
Composable Matchers
terminal_spec.rb
describe Terminal, "#printer" do let(:epson) { stub "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } subject { terminal.printer }! before do Printer.stub(:by_ip).and_return(epson) end! it { should eq epson }end
RSpec 2.X doubles
terminal_spec.rb
describe Terminal, "#printer" do let(:epson) { double "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } subject { terminal.printer }! before do allow(Printer).to receive_messages(by_ip: epson) end! it { is_expected.to eq epson }end
RSpec 3.0 doubles
terminal_spec.rb
describe Terminal, "#printer" do let(:epson) { double "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } subject { terminal.printer }! before do allow(Printer).to receive_messages(by_ID: epson) end! it { is_expected.to eq epson }end
Double checking™
terminal_spec.rb
describe Terminal, "#printer" do let(:epson) { stub "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } subject { terminal.printer }! before do Printer.should_receive(:by_ip) .with("1.1.1.1").and_return(epson) end! it { should eq epson }end
RSpec 2.X message expectation
terminal_spec.rb
describe Terminal, "#printer" do let(:epson) { double "Espon 5000" } let(:terminal) { Terminal.new(:ip => "1.1.1.1") } before do allow(Printer).to receive_messages(by_ip: epson) end subject! { terminal.printer }! it { is_expected.to eq epson } it "finds printer by IP" do expect(Printer).to \ have_received(:by_ip).with("1.1.1.1") end
RSpec 3.0 message expectation
Links
support/configure.rb
Its:github.com/rspec/rspec-its!Collection Matchers:github.com/rspec/rspec-collection_matchers!RSpec Rails:github.com/rspec/rspec-rails!ActiveModel mocks:github.com/rspec/rspec-activemodel-mocks
Libraries
support/configure.rb
Step-by-step upgrade instructions:relishapp.com/rspec/docs/upgrade!RDoc:rubydoc.info/github/rspec/rspec-expectations!Features as documentation:relishapp.com/rspec
Documentation
support/configure.rb
Transpec, the RSpec syntax conversion tool:yujinakayama.me/transpec/!Myron Marston’s blog:myronmars.to
Resources
CHEERS
spec/user_spec.rb
describe User, ".admin_names" do let(:admin) { User.create!(:admin => true, :first => "Clark", :last => "Kent")} let(:avg_joe) { User.create!(:admin => false, :first => "Joe", :last => "Shmo")} subject { admin_names }! it { should include "Clark Kent" } it { should_not include "Joe Shmo" }end
let() gotcha
spec/user_spec.rb
describe User, ".admin_names" do let(:admin) { User.create!(:admin => true, :first => "Clark", :last => "Kent")} let(:avg_joe) { User.create!(:admin => false, :first => "Joe", :last => "Shmo")} subject { User.admin_names } before { admin && avg_joe }! it { should include "Clark Kent" } it { should_not include "Joe Shmo" }end
let() work-around
spec/user_spec.rb
describe User, ".admin_names" do let!(:admin) { User.create!(:admin => true, :first => "Clark", :last => "Kent")} let!(:avg_joe) { User.create!(:admin => false, :first => "Joe", :last => "Shmo")} subject { User.admin_names }! it { should include "Clark Kent" } it { should_not include "Joe Shmo" }end
let!()