RSpec 3.0: Under the Covers
Achieving“Zero Monkey-Patching Mode”
Brian Gesiak
March 13th, 2014
Research Student, The University of Tokyo
@modocache
Today
• Monkey patching
• How does RSpec work?
• The rspec executable
• Loading spec files
• Example groups: describe and context
• RSpec 2.11: describe no longer added to every
Object
• Running examples (it blocks)
• Expectations
• RSpec 2.11: expect-based syntax removes need for
adding should to every Object
Monkey Patching
How to Do It and Why You Shouldn’t
class Array
def sum # Also defined in `activesupport`!
inject { |sum, x| sum + x }
end
end
!
expect([1, 2, 3].sum).to eq 6
Monkey Patching
How to Do It and Why You Shouldn’t
class Array
def sum # Also defined in `activesupport`!
inject { |sum, x| sum + x }
end
end
!
expect([1, 2, 3].sum).to eq 6
Monkey Patching
How to Do It and Why You Shouldn’t
class Array
def sum # Also defined in `activesupport`!
inject { |sum, x| sum + x }
end
end
!
expect([1, 2, 3].sum).to eq 6
Monkey patching can lead to cryptic errors
Monkey Patching Root Objects
Go Big or Go Home
class Object
def should
# ...
end
end
module Kernel
def describe
# ...
end
end
# Global method
describe
# All objects respond
# to method
Object.new.should
Monkey Patching Root Objects
Go Big or Go Home
class Object
def should
# ...
end
end
module Kernel
def describe
# ...
end
end
# Global method
describe
# All objects respond
# to method
Object.new.should
Monkey Patching Root Objects
Go Big or Go Home
class Object
def should
# ...
end
end
module Kernel
def describe
# ...
end
end
# Global method
describe
# All objects respond
# to method
Object.new.should
Monkey Patching Root Objects
Go Big or Go Home
class Object
def should
# ...
end
end
module Kernel
def describe
# ...
end
end
# Global method
describe
# All objects respond
# to method
Object.new.should
Monkey Patching Root Objects
Go Big or Go Home
class Object
def should
# ...
end
end
module Kernel
def describe
# ...
end
end
# Global method
describe
# All objects respond
# to method
Object.new.should
RSpec 3.0
Historically, RSpec has extensively used monkey
patching to create its readable syntax, adding
methods…to every object.
!
In the last few 2.x releases, we’ve worked towards
reducing the amount of monkey patching done by
RSpec.
Zero Monkey-Patching Mode
Myron Marston, RSpec Core Member
@myronmarston
RSpec 3.0
Achieving“Zero Monkey-Patching Mode”
$ rspec meetup_spec.rb
The rspec Executable
rspec-core/exe/rspec
#!/usr/bin/env ruby
!
require 'rspec/core'
RSpec::Core::Runner.invoke
The rspec Executable
rspec-core/exe/rspec
#!/usr/bin/env ruby
!
require 'rspec/core'
RSpec::Core::Runner.invoke
Loading Spec Files
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
RSpec::Core::CommandLine.run
Loading Spec Files
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
RSpec::Core::CommandLine.run
Loading Spec Files
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
RSpec::Core::CommandLine.run
Loading Spec Files
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
RSpec::Core::CommandLine.run
Loading Spec Files
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
RSpec::Core::CommandLine.run
Loading Spec Files
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
RSpec::Core::CommandLine.run
Loading Spec Files
RSpec::Core::Configuration.load_spec_files
def load_spec_files
files_to_run.uniq.each { |f| load f }
@spec_files_loaded = true
end
Loading Spec Files
RSpec::Core::Configuration.load_spec_files
def load_spec_files
files_to_run.uniq.each { |f| load f }
@spec_files_loaded = true
end
Building Example Groups
describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
context 'without a chef' do
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?)
.to be_true
end
end
end
end
Loading a Typical Spec File
Building Example Groups
describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
context 'without a chef' do
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?)
.to be_true
end
end
end
end
Loading a Typical Spec File
Where Does describe Come From?
RSpec::Core::DSL
def self.expose_example_group_alias(name)
example_group_aliases << name
!
(class << RSpec; self; end).
__send__(:define_method, name) # ...
!
expose_example_group_alias_globally(name) if
exposed_globally? # defines on Module
end
Where Does describe Come From?
RSpec::Core::DSL
def self.expose_example_group_alias(name)
example_group_aliases << name
!
(class << RSpec; self; end).
__send__(:define_method, name) # ...
!
expose_example_group_alias_globally(name) if
exposed_globally? # defines on Module
end
Where Does describe Come From?
RSpec::Core::DSL
def self.expose_example_group_alias(name)
example_group_aliases << name
!
(class << RSpec; self; end).
__send__(:define_method, name) # ...
!
expose_example_group_alias_globally(name) if
exposed_globally? # defines on Module
end
Where Does describe Come From?
RSpec::Core::DSL
def self.expose_example_group_alias(name)
example_group_aliases << name
!
(class << RSpec; self; end).
__send__(:define_method, name) # ...
!
expose_example_group_alias_globally(name) if
exposed_globally? # defines on Module
end
Disabling Module Monkey Patching
RSpec.configure do |config|
config.expose_dsl_globally = false
end
!
RSpec.describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
# ...
end
end
Only Top Level describe Blocks Are Affected
Disabling Module Monkey Patching
RSpec.configure do |config|
config.expose_dsl_globally = false
end
!
RSpec.describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
# ...
end
end
Only Top Level describe Blocks Are Affected
Disabling Module Monkey Patching
RSpec.configure do |config|
config.expose_dsl_globally = false
end
!
RSpec.describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
# ...
end
end
Only Top Level describe Blocks Are Affected
Disabling Module Monkey Patching
RSpec.configure do |config|
config.expose_dsl_globally = false
end
!
RSpec.describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
# ...
end
end
Only Top Level describe Blocks Are Affected
Opt-in as of RSpec 2.11
Building Example Groups
describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
context 'without a chef' do
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?)
.to be_true
end
end
end
end
Loading a Typical Spec File
Building Example Groups
describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
context 'without a chef' do
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?)
.to be_true
end
end
end
end
Loading a Typical Spec File
Building Example Groups
describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
context 'without a chef' do
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?)
.to be_true
end
end
end
end
Loading a Typical Spec File
Building Example Groups
describe TokyoRailsMeetup do
let(:meetup) { described_class.new }
describe '#start' do
context 'without a chef' do
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?)
.to be_true
end
end
end
end
Loading a Typical Spec File
Building Example Groups
Hierarchies of Example Groups and Examples
class ExampleGroup
def self.example_group(*args,
&example_group_block)
# ...
child = subclass(self,
args,
&example_group_block)
children << child
child
end
!
def self.examples
@examples ||= []
end
# ...
end
Building Example Groups
Hierarchies of Example Groups and Examples
class ExampleGroup
def self.example_group(*args,
&example_group_block)
# ...
child = subclass(self,
args,
&example_group_block)
children << child
child
end
!
def self.examples
@examples ||= []
end
# ...
end
Building Example Groups
Hierarchies of Example Groups and Examples
class ExampleGroup
def self.example_group(*args,
&example_group_block)
# ...
child = subclass(self,
args,
&example_group_block)
children << child
child
end
!
def self.examples
@examples ||= []
end
# ...
end
Building Example Groups
Hierarchies of Example Groups and Examples
class ExampleGroup
def self.example_group(*args,
&example_group_block)
# ...
child = subclass(self,
args,
&example_group_block)
children << child
child
end
!
def self.examples
@examples ||= []
end
# ...
end
Building Example Groups
Hierarchies of Example Groups and Examples
Running the Examples
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
RSpec::Core::CommandLine.run
Running the Examples
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
RSpec::Core::CommandLine.run
Running the Examples
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
RSpec::Core::CommandLine.run
Running the Examples
RSpec::Core::Example.run
def run(example_group_instance, reporter)
# ...
begin
run_before_each
@example_group_instance.
instance_exec(self, &@example_block)
rescue Exception => e
set_exception(e)
ensure
run_after_each
end
end
Running the Examples
RSpec::Core::Example.run
def run(example_group_instance, reporter)
# ...
begin
run_before_each
@example_group_instance.
instance_exec(self, &@example_block)
rescue Exception => e
set_exception(e)
ensure
run_after_each
end
end
Running the Examples
RSpec::Core::Example.run
def run(example_group_instance, reporter)
# ...
begin
run_before_each
@example_group_instance.
instance_exec(self, &@example_block)
rescue Exception => e
set_exception(e)
ensure
run_after_each
end
end
Running the Examples
RSpec::Core::Example.run
def run(example_group_instance, reporter)
# ...
begin
run_before_each
@example_group_instance.
instance_exec(self, &@example_block)
rescue Exception => e
set_exception(e)
ensure
run_after_each
end
end
Making Expectations
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
!
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?).to be_true
end
The Deprecated should Syntax
Making Expectations
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
!
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?).to be_true
end
The Deprecated should Syntax
How should is Monkey Patched
class Configuration
# ...
def syntax=(values)
if Array(values).include?(:expect)
Expectations::Syntax.enable_expect
else
Expectations::Syntax.disable_expect
end
!
if Array(values).include?(:should)
Expectations::Syntax.enable_should
else
Expectations::Syntax.disable_should
end
end
end
RSpec::Expectations::Configuration
How should is Monkey Patched
class Configuration
# ...
def syntax=(values)
if Array(values).include?(:expect)
Expectations::Syntax.enable_expect
else
Expectations::Syntax.disable_expect
end
!
if Array(values).include?(:should)
Expectations::Syntax.enable_should
else
Expectations::Syntax.disable_should
end
end
end
RSpec::Expectations::Configuration
How should is Monkey Patched
class Configuration
# ...
def syntax=(values)
if Array(values).include?(:expect)
Expectations::Syntax.enable_expect
else
Expectations::Syntax.disable_expect
end
!
if Array(values).include?(:should)
Expectations::Syntax.enable_should
else
Expectations::Syntax.disable_should
end
end
end
RSpec::Expectations::Configuration
How should is Monkey Patched
class Configuration
# ...
def syntax=(values)
if Array(values).include?(:expect)
Expectations::Syntax.enable_expect
else
Expectations::Syntax.disable_expect
end
!
if Array(values).include?(:should)
Expectations::Syntax.enable_should
else
Expectations::Syntax.disable_should
end
end
end
RSpec::Expectations::Configuration
How should is Monkey Patched
def enable_should(syntax_host=::Object.ancestors.last)
# ...
syntax_host.module_exec do
def should(matcher=nil, message=nil, &block)
# ...
end
!
def should_not(matcher=nil, message=nil, &block)
# ...
end
end
end
RSpec::Expectations::Syntax
How should is Monkey Patched
def enable_should(syntax_host=::Object.ancestors.last)
# ...
syntax_host.module_exec do
def should(matcher=nil, message=nil, &block)
# ...
end
!
def should_not(matcher=nil, message=nil, &block)
# ...
end
end
end
RSpec::Expectations::Syntax
How should is Monkey Patched
def enable_should(syntax_host=::Object.ancestors.last)
# ...
syntax_host.module_exec do
def should(matcher=nil, message=nil, &block)
# ...
end
!
def should_not(matcher=nil, message=nil, &block)
# ...
end
end
end
RSpec::Expectations::Syntax
Making Expectations
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
!
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?).to be_true
end
The New expect(…).to Syntax
Making Expectations
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
!
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?).to be_true
end
The New expect(…).to Syntax
Making Expectations
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
!
it 'gets everyone drunk' do
meetup.start
expect(meetup.everyone_wasted?).to be_true
end
The New expect(…).to Syntax
How expect is Implemented
def enable_expect(syntax_host=::RSpec::Matchers)
# ...
syntax_host.module_exec do
def expect(*target, &target_block)
# ...
end
end
end
RSpec::Expectations::Syntax
How expect is Implemented
def enable_expect(syntax_host=::RSpec::Matchers)
# ...
syntax_host.module_exec do
def expect(*target, &target_block)
# ...
end
end
end
RSpec::Expectations::Syntax
How expect is Implemented
def enable_expect(syntax_host=::RSpec::Matchers)
# ...
syntax_host.module_exec do
def expect(*target, &target_block)
# ...
end
end
end
RSpec::Expectations::Syntax
Disabling should Monkey Patching
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
c.syntax = :expect
end
end
!
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
Disabling should Monkey Patching
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
c.syntax = :expect
end
end
!
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
Disabling should Monkey Patching
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
c.syntax = :expect
end
end
!
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
Disabling should Monkey Patching
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
c.syntax = :expect
end
end
!
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
Disabling should Monkey Patching
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
c.syntax = :expect
end
end
!
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
expect(meetup).to be_hoppin
Disabling should Monkey Patching
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
c.syntax = :expect
end
end
!
it 'is a grand old time' do
meetup.start
meetup.should be_hoppin
end
expect(meetup).to be_hoppin
should Emits Deprecation Warning as of RSpec 3.0
Bringing it All Together
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
Load the Specs & Build Example Groups, then Run
Bringing it All Together
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
Load the Specs & Build Example Groups, then Run
Bringing it All Together
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
Load the Specs & Build Example Groups, then Run
Bringing it All Together
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
Load the Specs & Build Example Groups, then Run
Bringing it All Together
class CommandLine
def run(err, out)
# ...
@configuration.load_spec_files
# ...
begin
@configuration.hooks.run(:before, :suite)
@world.ordered_example_groups.map {
|g| g.run(reporter) } # ...
ensure
@configuration.hooks.run(:after, :suite)
end
end
end
Load the Specs & Build Example Groups, then Run
Want to Learn More about RSpec?
• http://modocache.io/rspec-under-the-covers
• Expectations in RSpec 3.0
• RSpec Output Formatting
• Shared Examples in RSpec
!
• Follow me on Twitter and GitHub at @modocache
• First person to tweet me gets an Atom invite! #swag
!
• Myron Marston’s blog: http://myronmars.to/n/dev-blog

RSpec 3.0: Under the Covers

  • 1.
    RSpec 3.0: Underthe Covers Achieving“Zero Monkey-Patching Mode” Brian Gesiak March 13th, 2014 Research Student, The University of Tokyo @modocache
  • 2.
    Today • Monkey patching •How does RSpec work? • The rspec executable • Loading spec files • Example groups: describe and context • RSpec 2.11: describe no longer added to every Object • Running examples (it blocks) • Expectations • RSpec 2.11: expect-based syntax removes need for adding should to every Object
  • 3.
    Monkey Patching How toDo It and Why You Shouldn’t class Array def sum # Also defined in `activesupport`! inject { |sum, x| sum + x } end end ! expect([1, 2, 3].sum).to eq 6
  • 4.
    Monkey Patching How toDo It and Why You Shouldn’t class Array def sum # Also defined in `activesupport`! inject { |sum, x| sum + x } end end ! expect([1, 2, 3].sum).to eq 6
  • 5.
    Monkey Patching How toDo It and Why You Shouldn’t class Array def sum # Also defined in `activesupport`! inject { |sum, x| sum + x } end end ! expect([1, 2, 3].sum).to eq 6 Monkey patching can lead to cryptic errors
  • 6.
    Monkey Patching RootObjects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • 7.
    Monkey Patching RootObjects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • 8.
    Monkey Patching RootObjects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • 9.
    Monkey Patching RootObjects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • 10.
    Monkey Patching RootObjects Go Big or Go Home class Object def should # ... end end module Kernel def describe # ... end end # Global method describe # All objects respond # to method Object.new.should
  • 11.
    RSpec 3.0 Historically, RSpechas extensively used monkey patching to create its readable syntax, adding methods…to every object. ! In the last few 2.x releases, we’ve worked towards reducing the amount of monkey patching done by RSpec. Zero Monkey-Patching Mode Myron Marston, RSpec Core Member @myronmarston
  • 12.
    RSpec 3.0 Achieving“Zero Monkey-PatchingMode” $ rspec meetup_spec.rb
  • 13.
    The rspec Executable rspec-core/exe/rspec #!/usr/bin/envruby ! require 'rspec/core' RSpec::Core::Runner.invoke
  • 14.
    The rspec Executable rspec-core/exe/rspec #!/usr/bin/envruby ! require 'rspec/core' RSpec::Core::Runner.invoke
  • 15.
    Loading Spec Files classCommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • 16.
    Loading Spec Files classCommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • 17.
    Loading Spec Files classCommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • 18.
    Loading Spec Files classCommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • 19.
    Loading Spec Files classCommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • 20.
    Loading Spec Files classCommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • 21.
    Loading Spec Files RSpec::Core::Configuration.load_spec_files defload_spec_files files_to_run.uniq.each { |f| load f } @spec_files_loaded = true end
  • 22.
    Loading Spec Files RSpec::Core::Configuration.load_spec_files defload_spec_files files_to_run.uniq.each { |f| load f } @spec_files_loaded = true end
  • 23.
    Building Example Groups describeTokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • 24.
    Building Example Groups describeTokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • 25.
    Where Does describeCome From? RSpec::Core::DSL def self.expose_example_group_alias(name) example_group_aliases << name ! (class << RSpec; self; end). __send__(:define_method, name) # ... ! expose_example_group_alias_globally(name) if exposed_globally? # defines on Module end
  • 26.
    Where Does describeCome From? RSpec::Core::DSL def self.expose_example_group_alias(name) example_group_aliases << name ! (class << RSpec; self; end). __send__(:define_method, name) # ... ! expose_example_group_alias_globally(name) if exposed_globally? # defines on Module end
  • 27.
    Where Does describeCome From? RSpec::Core::DSL def self.expose_example_group_alias(name) example_group_aliases << name ! (class << RSpec; self; end). __send__(:define_method, name) # ... ! expose_example_group_alias_globally(name) if exposed_globally? # defines on Module end
  • 28.
    Where Does describeCome From? RSpec::Core::DSL def self.expose_example_group_alias(name) example_group_aliases << name ! (class << RSpec; self; end). __send__(:define_method, name) # ... ! expose_example_group_alias_globally(name) if exposed_globally? # defines on Module end
  • 29.
    Disabling Module MonkeyPatching RSpec.configure do |config| config.expose_dsl_globally = false end ! RSpec.describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do # ... end end Only Top Level describe Blocks Are Affected
  • 30.
    Disabling Module MonkeyPatching RSpec.configure do |config| config.expose_dsl_globally = false end ! RSpec.describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do # ... end end Only Top Level describe Blocks Are Affected
  • 31.
    Disabling Module MonkeyPatching RSpec.configure do |config| config.expose_dsl_globally = false end ! RSpec.describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do # ... end end Only Top Level describe Blocks Are Affected
  • 32.
    Disabling Module MonkeyPatching RSpec.configure do |config| config.expose_dsl_globally = false end ! RSpec.describe TokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do # ... end end Only Top Level describe Blocks Are Affected Opt-in as of RSpec 2.11
  • 33.
    Building Example Groups describeTokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • 34.
    Building Example Groups describeTokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • 35.
    Building Example Groups describeTokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • 36.
    Building Example Groups describeTokyoRailsMeetup do let(:meetup) { described_class.new } describe '#start' do context 'without a chef' do it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?) .to be_true end end end end Loading a Typical Spec File
  • 37.
    Building Example Groups Hierarchiesof Example Groups and Examples class ExampleGroup def self.example_group(*args, &example_group_block) # ... child = subclass(self, args, &example_group_block) children << child child end ! def self.examples @examples ||= [] end # ... end
  • 38.
    Building Example Groups Hierarchiesof Example Groups and Examples class ExampleGroup def self.example_group(*args, &example_group_block) # ... child = subclass(self, args, &example_group_block) children << child child end ! def self.examples @examples ||= [] end # ... end
  • 39.
    Building Example Groups Hierarchiesof Example Groups and Examples class ExampleGroup def self.example_group(*args, &example_group_block) # ... child = subclass(self, args, &example_group_block) children << child child end ! def self.examples @examples ||= [] end # ... end
  • 40.
    Building Example Groups Hierarchiesof Example Groups and Examples class ExampleGroup def self.example_group(*args, &example_group_block) # ... child = subclass(self, args, &example_group_block) children << child child end ! def self.examples @examples ||= [] end # ... end
  • 41.
    Building Example Groups Hierarchiesof Example Groups and Examples
  • 42.
    Running the Examples classCommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • 43.
    Running the Examples classCommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • 44.
    Running the Examples classCommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end RSpec::Core::CommandLine.run
  • 45.
    Running the Examples RSpec::Core::Example.run defrun(example_group_instance, reporter) # ... begin run_before_each @example_group_instance. instance_exec(self, &@example_block) rescue Exception => e set_exception(e) ensure run_after_each end end
  • 46.
    Running the Examples RSpec::Core::Example.run defrun(example_group_instance, reporter) # ... begin run_before_each @example_group_instance. instance_exec(self, &@example_block) rescue Exception => e set_exception(e) ensure run_after_each end end
  • 47.
    Running the Examples RSpec::Core::Example.run defrun(example_group_instance, reporter) # ... begin run_before_each @example_group_instance. instance_exec(self, &@example_block) rescue Exception => e set_exception(e) ensure run_after_each end end
  • 48.
    Running the Examples RSpec::Core::Example.run defrun(example_group_instance, reporter) # ... begin run_before_each @example_group_instance. instance_exec(self, &@example_block) rescue Exception => e set_exception(e) ensure run_after_each end end
  • 49.
    Making Expectations it 'isa grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The Deprecated should Syntax
  • 50.
    Making Expectations it 'isa grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The Deprecated should Syntax
  • 51.
    How should isMonkey Patched class Configuration # ... def syntax=(values) if Array(values).include?(:expect) Expectations::Syntax.enable_expect else Expectations::Syntax.disable_expect end ! if Array(values).include?(:should) Expectations::Syntax.enable_should else Expectations::Syntax.disable_should end end end RSpec::Expectations::Configuration
  • 52.
    How should isMonkey Patched class Configuration # ... def syntax=(values) if Array(values).include?(:expect) Expectations::Syntax.enable_expect else Expectations::Syntax.disable_expect end ! if Array(values).include?(:should) Expectations::Syntax.enable_should else Expectations::Syntax.disable_should end end end RSpec::Expectations::Configuration
  • 53.
    How should isMonkey Patched class Configuration # ... def syntax=(values) if Array(values).include?(:expect) Expectations::Syntax.enable_expect else Expectations::Syntax.disable_expect end ! if Array(values).include?(:should) Expectations::Syntax.enable_should else Expectations::Syntax.disable_should end end end RSpec::Expectations::Configuration
  • 54.
    How should isMonkey Patched class Configuration # ... def syntax=(values) if Array(values).include?(:expect) Expectations::Syntax.enable_expect else Expectations::Syntax.disable_expect end ! if Array(values).include?(:should) Expectations::Syntax.enable_should else Expectations::Syntax.disable_should end end end RSpec::Expectations::Configuration
  • 55.
    How should isMonkey Patched def enable_should(syntax_host=::Object.ancestors.last) # ... syntax_host.module_exec do def should(matcher=nil, message=nil, &block) # ... end ! def should_not(matcher=nil, message=nil, &block) # ... end end end RSpec::Expectations::Syntax
  • 56.
    How should isMonkey Patched def enable_should(syntax_host=::Object.ancestors.last) # ... syntax_host.module_exec do def should(matcher=nil, message=nil, &block) # ... end ! def should_not(matcher=nil, message=nil, &block) # ... end end end RSpec::Expectations::Syntax
  • 57.
    How should isMonkey Patched def enable_should(syntax_host=::Object.ancestors.last) # ... syntax_host.module_exec do def should(matcher=nil, message=nil, &block) # ... end ! def should_not(matcher=nil, message=nil, &block) # ... end end end RSpec::Expectations::Syntax
  • 58.
    Making Expectations it 'isa grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The New expect(…).to Syntax
  • 59.
    Making Expectations it 'isa grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The New expect(…).to Syntax
  • 60.
    Making Expectations it 'isa grand old time' do meetup.start meetup.should be_hoppin end ! it 'gets everyone drunk' do meetup.start expect(meetup.everyone_wasted?).to be_true end The New expect(…).to Syntax
  • 61.
    How expect isImplemented def enable_expect(syntax_host=::RSpec::Matchers) # ... syntax_host.module_exec do def expect(*target, &target_block) # ... end end end RSpec::Expectations::Syntax
  • 62.
    How expect isImplemented def enable_expect(syntax_host=::RSpec::Matchers) # ... syntax_host.module_exec do def expect(*target, &target_block) # ... end end end RSpec::Expectations::Syntax
  • 63.
    How expect isImplemented def enable_expect(syntax_host=::RSpec::Matchers) # ... syntax_host.module_exec do def expect(*target, &target_block) # ... end end end RSpec::Expectations::Syntax
  • 64.
    Disabling should MonkeyPatching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end
  • 65.
    Disabling should MonkeyPatching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end
  • 66.
    Disabling should MonkeyPatching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end
  • 67.
    Disabling should MonkeyPatching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end
  • 68.
    Disabling should MonkeyPatching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end expect(meetup).to be_hoppin
  • 69.
    Disabling should MonkeyPatching RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] c.syntax = :expect end end ! it 'is a grand old time' do meetup.start meetup.should be_hoppin end expect(meetup).to be_hoppin should Emits Deprecation Warning as of RSpec 3.0
  • 70.
    Bringing it AllTogether class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • 71.
    Bringing it AllTogether class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • 72.
    Bringing it AllTogether class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • 73.
    Bringing it AllTogether class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • 74.
    Bringing it AllTogether class CommandLine def run(err, out) # ... @configuration.load_spec_files # ... begin @configuration.hooks.run(:before, :suite) @world.ordered_example_groups.map { |g| g.run(reporter) } # ... ensure @configuration.hooks.run(:after, :suite) end end end Load the Specs & Build Example Groups, then Run
  • 75.
    Want to LearnMore about RSpec? • http://modocache.io/rspec-under-the-covers • Expectations in RSpec 3.0 • RSpec Output Formatting • Shared Examples in RSpec ! • Follow me on Twitter and GitHub at @modocache • First person to tweet me gets an Atom invite! #swag ! • Myron Marston’s blog: http://myronmars.to/n/dev-blog