Straight Up RSpec 3 - a neat Ruby BDD tool

Preview:

DESCRIPTION

Upgrading, going beyond the basics, introducing some elegant features and promoting readability.

Citation preview

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!()