11. Martini Spec Draft
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=)
end
end
martini_spec.rb
12. Custom Matcher
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
have_attribute.rb
13. Custom Matcher
describe "Barkeep::Martini" do
include AttributeMatchers
before do
@martini = Barkeep::Martini.new
end
it "should have an attribute named booze" do
@martini.should have_attribute(:booze)
end
it "should have an attribute named garnish" do
@martini.should have_attribute(:garnish)
end
end
martini_spec.rb
14. subject()
describe "Barkeep::Martini" do
include AttributeMatchers
subject { Barkeep::Martini.new }
it "should have an attribute named booze" do
subject.should have_attribute(:booze)
end
it "should have an attribute named garnish" do
subject.should have_attribute(:garnish)
end
end
martini_spec.rb
15. Implicit subject()
describe "Barkeep::Martini" do
include AttributeMatchers
subject { Barkeep::Martini.new }
it "should have an attribute named booze" do
should have_attribute(:booze)
end
it "should have an attribute named garnish" do
should have_attribute(:garnish)
end
end
martini_spec.rb
16. DRY Messages
describe "Barkeep::Martini" do
include AttributeMatchers
subject { Barkeep::Martini.new }
it { should have_attribute(:booze) }
it { should have_attribute(:garnish) }
end
martini_spec.rb
20. Original Draft
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=)
end
end
martini_spec.rb
22. Martini Spec
describe Barkeep::Martini do
it { should have_attribute(:booze) }
it { should have_attribute(:glass) }
it { should have_attribute(:garnish) }
end
martini_spec.rb
23. Shared Example Group
share_examples_for "a drink" do
it { should have_attribute(:booze) }
it { should have_attribute(:glass) }
end
support/examples/drink.rb
26. Whiskey & Martini #ingredients
share_examples_for "a drink" do
it { should have_attribute(:booze) }
it { should have_attribute(:glass) }
it "should have an ingredients Array" do
subject.ingredients.should be_a Array
end
end
support/examples/drink.rb
27. context()
describe Barkeep::Martini do
it_should_behave_like "a drink"
it { should have_attribute(:garnish) }
it { should have_attribute(:mixer) }
context "with a mixer" do
before do
@mixer = 'vermouth'
subject.mixer = @mixer
end
it "should include mixer in ingredients" do
subject.ingredients.should include @mixer
end
end
martini_spec.rb
28. let()
describe Barkeep::Martini do
it_should_behave_like "a drink"
it { should have_attribute(:garnish) }
it { should have_attribute(:mixer) }
context "with a mixer" do
let(:mixer) { 'vermouth' }
before { subject.mixer = mixer }
it "should include mixer in ingredients" do
subject.ingredients.should include mixer
end
end
end
martini_spec.rb
29. its()
describe Barkeep::Martini do
it_should_behave_like "a drink"
it { should have_attribute(:garnish) }
it { should have_attribute(:mixer) }
context "with a mixer" do
let(:mixer) { 'vermouth' }
before { subject.mixer = mixer }
its(:ingredients) { should include mixer }
end
end
martini_spec.rb
30. its()
describe Barkeep::Martini do
it_should_behave_like "a drink"
it { should have_attribute(:garnish) }
it { should have_attribute(:mixer) }
end
describe Barkeep::Martini, ".new with mixer" do
let(:mixer) { 'vermouth' }
subject { Barkeep::Martini.new(:mixer => mixer) }
its(:ingredients) { should include mixer }
end
martini_spec.rb
31. have()
describe Barkeep::Martini, ".new with mixer" do
let(:mixer) { 'vermouth' }
subject { Barkeep::Martini.new(:mixer => mixer) }
its(:ingredients) { should include mixer }
its(:ingredients) { should have(1).items }
end
martini_spec.rb
32. have()
describe Barkeep::Martini, ".new with mixer" do
let(:mixer) { 'vermouth' }
subject { Barkeep::Martini.new(:mixer => mixer) }
its(:ingredients) { should include mixer }
it { should have(1).ingredients }
end
martini_spec.rb
33. expect()
describe Barkeep::Martini do
let(:mixer) { 'vermouth' }
it "should include mixer in ingredients" do
expect { subject.mixer = mixer }.
to change { subject.ingredients }.
from( [] ).
to [ mixer ]
end
end
martini_spec.rb
34. stub()
describe Barkeep::Terminal, "#printer" do
let(:ones) { "1.1.1.1" }
let(:epson) { double("Espon 5000") }
subject { Barkeep::Terminal.new(:ip => ones) }
before do
Printer.stub(:new) { epson }
end
it "should have a printer" do
subject.printer.should eq epson
end
end
terminal_spec.rb
35. should_receive()
describe Barkeep::Terminal, "#printer" do
let(:ones) { "1.1.1.1" }
let(:epson) { double("Espon 5000") }
subject { Barkeep::Terminal.new(:ip => ones) }
it "should have a printer" do
Printer.should_receive(:new).
with(:ip => ones) { epson }
subject.printer
end
end
terminal_spec.rb
37. RSpec.configure
RSpec.configure do |config|
config.include MyMartiniMatchers
config.include ModelHelpers, :type => :model
end
# spec/model/martini_spec.rb
describe Martini, :type => :model do
…
end
support/configure.rb
38. RSpec.configure
RSpec.configure do |config|
config.before(:all) {} # run once
config.before(:each) {} # run before each example
config.after (:each) {} # run after each example
config.after (:all) {} # run once
# run before each example of type :model
config.before(:each, :type => :model) {}
end
support/configure.rb
39. Inclusion
RSpec.configure do |c|
c.filter_run :focus => true
c.run_all_when_everything_filtered = true
end
# in any spec file
describe "something" do
it "does something", :focus => true do
# ....
end
end
support/configure.rb
40. Exclusion
RSpec.configure do |c|
c.exclusion_filter = { :ruby => lambda {|version|
!(RUBY_VERSION.to_s =~ /^#{version.to_s}/)
}}
end
# in any spec file
describe "something" do
it "does something", :ruby => 1.8 do
# ....
end
it "does something", :ruby => 1.9 do
# ....
support/configure.rb
45. describe User do
it { should belong_to(:account) }
it { should have_many(:posts) }
it { should validate_presence_of(:email) }
it { should allow_value("f@b.com").for(:email) }
it { should_not allow_value("test").for(:email) }
end
47. let() gotcha
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
spec/user_spec.rb
48. let() work-around
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
spec/user_spec.rb
49. let!()
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
spec/user_spec.rb
Editor's Notes
\n
going beyond the basics\noffering some opinions\nhighlighting some (relatively) new hotness\n
\n
\n
partial simple project directory structure\n
Keep spec_helper slim.\n\nInclude it in all spec files.\n\nEspecially in a Rails project. You’ll want to regenerate it when you update RSpec.\n\nMove Helpers into spec/support/. I even move configuration there (configure.rb).\n
Command line options that can be specified in .rspec when using the RSpec Rake task.\n
Output format\n
Line number of a context/describe block or example\n
tags\n
An exercise in refactoring some specs\n
Original spec\n
This example uses the RSpec matcher DSL.\n\nMatchers can be created from scratch as well. To do so you need:\nA module with a method (e.g. have_attribute()) that instantiates a class\nThe class must define matches?(target), description(), failure_message_for_should(), failure_message_for_should_not()\n
Using our new custom matcher, have_attribute()\n
\n
implicit subject()\n\ncall “should” off “nothing”\n
Without redundant example (it) messages\n
Derived subject().\n\nUse Class, not String as argument to describe()\n
\n
If AttributeMatchers are included in Spec::Runner configuration, we don’t need to include it here\n
Original spec\n
Hmmm... Looks similar to martini_spec\n
Now both Whiskey and Martini have booze and glass attributes.\n\nLots of overlap.\n
Note that there are NO INSTANCE VARIABLES!\n\nWe use the subject() here.\n
The shared examples with use the derived subject().\n
\n
\n
Also note the “include” matcher!\n
There is a gotcha! More if we have time.\n
\n
OR\n\nlose the context() and break the example into its own describe() block\n\nNote the use of the “.” before the class method name (“new”). This is a common convention. “#” is used for instance methods\n
“items” is just sugar, we could use whatever word we want here e.g. “elements”\n
OR\n\na more readable example\n
I would not advocate this usage of expect().\nOther uses:\nexpect{}.to change{}.by(1)\nexpect{}.to raise_error\n\n
Any call to the .new() method on the Printer class will return epson.\n\nNote that epson is a test double(). It’s dumb. It doesn’t know how to do (or respond to) the real Printer methods.\n
Other test double libraries have test spies.\n
Configuring RSpec\n
Helpers included in RSpec configuration are included for all specs if no :type is specified.\n
BEWARE before(:all)!!! Not just in RSpec.configure, but in all specs.\n \n Who’s been burned by before(:all)?\n \n Improper use can lead to the unit test anti-pattern called “Interacting Tests” (or “Generous Leftovers”) and therefor erratic and hard to resolve failures.\n
Every example and example group comes with metadata information\n\nWhen you run the $ rspec command, only the examples that have :focus => true tag in the hash.\n\nIf there are no examples tagged with “:focus => true”, all examples will be run. \n
Do NOT run examples that are tagged with a different Ruby version when running the “rspec” command.\n
\n
Integrated fixture loading (and unloading).\nSpecial generators that generate RSpec code examples.\nHelp isolate your classes using example groups (e.g. Controller Examples don’t actually render Views)\nRails specific Matchers.\n
To name a few Matchers.\n
\n
Shoulda’s ActiveRecord Matchers.\n
\n
THIS WILL FAIL!\n
THIS WILL FAIL!\n
Green\n
Implementation possibility\n
Distributed (across cores, processors and even other machines) testing framework\n
A DRb test server implementation that forks itself every time your tests are run instead of unloading the RAILS constant.\n
Rspec::Matchers now offers you two approaches to differentiating between object identity.\n