$30 off During Our Annual Pro Sale. View Details »

RSpec: It Isn't Actually Magic

RSpec: It Isn't Actually Magic

Noel Rappin

April 22, 2015
Tweet

More Decks by Noel Rappin

Other Decks in Technology

Transcript

  1. Follow along at: http://bit.ly/rspec_magic

  2. Everybody loves RSpec

  3. None
  4. None
  5. None
  6. March, 2011

  7. RSpec is complicated

  8. While I think the benefits are discernable…

  9. This is mostly about the complicated

  10. Why is RSpec complicated? •RSpec is expressive •And very flexible

    •And it’s a big library
  11. How flexible is RSpec?

  12. With about 10 lines of configuration

  13. This flexible… RSpec.! Hotel do
 " "works" do
 #(1 +

    1).to ❤(2)
 end
 end
  14. RSpec: It’s not actually Magic Noel Rappin Table XI

  15. We’re going to look at RSpec source code

  16. Please don’t avert your eyes

  17. Why talk about RSpec internals?

  18. Talks should tell a story

  19. Once upon a time, there was a test… require 'rails_helper'


    
 RSpec.describe Name do
 it "produces a sort name" do
 name = Name.new("Noel", "Rappin")
 expect(name.sort_name).to eq("Rappin, Noel")
 end
 end
  20. RSpec with Parentheses require 'rails_helper'
 
 RSpec.describe(Name) do
 self.it(“produces a

    sort name”) do
 name = Name.new("Noel", "Rappin")
 self.expect(name.sort_name).to(self.eq(“Rappin, Noel”))
 end
 end
  21. Key RSpec words require 'rails_helper'
 
 RSpec.describe(Name) do
 self.it(“produces a

    sort name”) do
 name = Name.new("Noel", "Rappin")
 self.expect(name.sort_name).to(self.eq(“Rappin, Noel”))
 end
 end
  22. RSpec power words describe it expect to(something)

  23. RSpec power words describe it expect to(something) ExampleGroup Example ExpectationTarget

    Matcher
  24. require 'rails_helper'
 
 RSpec.describe(Name) do
 self.it(“produces a sort name”) do


    name = Name.new("Noel", "Rappin")
 self.expect(name.sort_name).to(self.eq(“Rappin, Noel”))
 end
 end
  25. require 'rails_helper'
 
 RSpec.describe(Name) do
 self.it(“produces a sort name”) do


    name = Name.new("Noel", "Rappin")
 self.expect(name.sort_name).to(self.eq(“Rappin, Noel”))
 end
 end Example Group
  26. require 'rails_helper'
 
 RSpec.describe(Name) do
 self.it(“produces a sort name”) do


    name = Name.new("Noel", "Rappin")
 self.expect(name.sort_name).to(self.eq(“Rappin, Noel”))
 end
 end Example Group Example
  27. require 'rails_helper'
 
 RSpec.describe(Name) do
 self.it(“produces a sort name”) do


    name = Name.new("Noel", "Rappin")
 self.expect(name.sort_name).to(self.eq(“Rappin, Noel”))
 end
 end Example Group Example ExpectationTarget
  28. require 'rails_helper'
 
 RSpec.describe(Name) do
 self.it(“produces a sort name”) do


    name = Name.new("Noel", "Rappin")
 self.expect(name.sort_name).to(self.eq(“Rappin, Noel”))
 end
 end Example Group Example ExpectationTarget Matcher
  29. Example Group • Created by describe or context • Arguments

    are a description, metadata, and block • Creates an anonymous subclass of ExampleGroup • Executes the block in the context of the class RSpec.describe Name, metadata: true do
 #stuff
 end
  30. Creating an ExampleGroup def self.subclass(parent, description, args, &example_group_block)
 subclass =

    Class.new(parent)
 subclass.set_it_up(description, *args, &example_group_block)
 subclass.module_exec(&example_group_block) if example_group_block
 MemoizedHelpers.define_helpers_on(subclass)
 subclass
 end
  31. Example Group Subclass def self.subclass(parent, description, args, &example_group_block)
 subclass =

    Class.new(parent)
 subclass.set_it_up(description, *args, &example_group_block)
 subclass.module_exec(&example_group_block) if example_group_block
 MemoizedHelpers.define_helpers_on(subclass)
 subclass
 end
  32. Setup (configure mock package) def self.subclass(parent, description, args, &example_group_block)
 subclass

    = Class.new(parent)
 subclass.set_it_up(description, *args, &example_group_block)
 subclass.module_exec(&example_group_block) if example_group_block
 MemoizedHelpers.define_helpers_on(subclass)
 subclass
 end
  33. Setup (configure mock package) def self.subclass(parent, description, args, &example_group_block)
 subclass

    = Class.new(parent)
 subclass.set_it_up(description, *args, &example_group_block)
 subclass.module_exec(&example_group_block) if example_group_block
 MemoizedHelpers.define_helpers_on(subclass)
 subclass
 end
  34. Setup (configure mock package) def self.subclass(parent, description, args, &example_group_block)
 subclass

    = Class.new(parent)
 subclass.set_it_up(description, *args, &example_group_block)
 subclass.module_exec(&example_group_block) if example_group_block
 MemoizedHelpers.define_helpers_on(subclass)
 subclass
 end
  35. What is module_exec? class Thing
 end
 
 Thing.module_exec(arg) do |arg|


    def hello() "Hello there!" end
 end 
 puts Thing.new.hello()
  36. Minimal RSpec with Parentheses require 'rails_helper'
 
 <<ExampleGroupSubclass>>.module_exec do
 it(“produces

    a sort name”) do
 name = Name.new("Noel", "Rappin")
 expect(name.sort_name).to(eq(“Rappin, Noel”))
 end
 end
  37. Example • Created by it, example, specify — instance methods

    of ExampleGroup • Arguments are description, metadata, and a block • Assigns the example to an array class attribute of the ExampleGroup it(“produces a sort name”) do
 #stuff
 end
  38. Creating an Example desc, *args = *all_args
 options = Metadata.build_hash_from(args)

    unless block
 options.update(:skip => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) end options.update(extra_options)
 examples << RSpec::Core::Example.new(self, desc, options, block)
 examples.last
  39. Creating an Example desc, *args = *all_args
 options = Metadata.build_hash_from(args)

    unless block
 options.update(:skip => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) end options.update(extra_options)
 examples << RSpec::Core::Example.new(self, desc, options, block)
 examples.last
  40. Creating an Example desc, *args = *all_args
 options = Metadata.build_hash_from(args)

    unless block
 options.update(:skip => RSpec::Core::Pending::NOT_YET_IMPLEMENTED) end options.update(extra_options)
 examples << RSpec::Core::Example.new(self, desc, options, block)
 examples.last
  41. What happens at run time? •RSpec creates an instance of

    the anonymous ExampleGroup class •The new instance runs all of its examples
  42. Running ExampleGroup runs examples ExampleGroup runs each example Example runs

  43. Running an Example Group def self.run(reporter=RSpec::Core::NullReporter)
 return if RSpec.world.wants_to_quit
 reporter.example_group_started(self)


    should_run_context_hooks = descendant_filtered_examples.any?
 begin
 run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks
 result_for_this_group = run_examples(reporter)
 results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all?
 result_for_this_group && results_for_descendants
 rescue Pending::SkipDeclaredInExample => ex
 for_filtered_examples(reporter) { |example| example.skip_with_exception(reporter, ex) }
 true
 rescue Exception => ex
 RSpec.world.wants_to_quit = true if fail_fast?
 for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) }
 false
 ensure
 run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks
 reporter.example_group_finished(self)
 end
 end
  44. Running an Example Group def self.run(reporter=RSpec::Core::NullReporter)
 return if RSpec.world.wants_to_quit
 reporter.example_group_started(self)


    should_run_context_hooks = descendant_filtered_examples.any?

  45. Running an Example Group begin
 run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks
 result_for_this_group

    = run_examples(reporter)
 results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all?
 result_for_this_group && results_for_descendants

  46. Running an Example Group 
 rescue Pending::SkipDeclaredInExample => ex
 for_filtered_examples(reporter)

    { |example| example.skip_with_exception(reporter, ex) }
 true
 rescue Exception => ex
 RSpec.world.wants_to_quit = true if fail_fast?
 for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) }
 false

  47. Running an Example Group 
 ensure
 run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks


    reporter.example_group_finished(self)
 end
 end
  48. Running an Example Group def self.run(reporter=RSpec::Core::NullReporter)
 return if RSpec.world.wants_to_quit
 reporter.example_group_started(self)


    should_run_context_hooks = descendant_filtered_examples.any?
 begin
 run_before_context_hooks(new('before(:context) hook')) if should_run_context_hooks
 result_for_this_group = run_examples(reporter)
 results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all?
 result_for_this_group && results_for_descendants
 rescue Pending::SkipDeclaredInExample => ex
 for_filtered_examples(reporter) { |example| example.skip_with_exception(reporter, ex) }
 true
 rescue Exception => ex
 RSpec.world.wants_to_quit = true if fail_fast?
 for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) }
 false
 ensure
 run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks
 reporter.example_group_finished(self)
 end
 end
  49. Running Examples def self.run_examples(reporter)
 ordering_strategy.order(filtered_examples).map do |example|
 next if RSpec.world.wants_to_quit


    instance = new(example.inspect_output)
 set_ivars(instance, before_context_ivars)
 succeeded = example.run(instance, reporter)
 RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
 succeeded
 end.all?
 end
  50. Running Examples def self.run_examples(reporter)
 ordering_strategy.order(filtered_examples).map do |example|
 next if RSpec.world.wants_to_quit


    instance = new(example.inspect_output)
 set_ivars(instance, before_context_ivars)
 succeeded = example.run(instance, reporter)
 RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
 succeeded
 end.all?
 end
  51. Running Examples def self.run_examples(reporter)
 ordering_strategy.order(filtered_examples).map do |example|
 next if RSpec.world.wants_to_quit


    instance = new(example.inspect_output)
 set_ivars(instance, before_context_ivars)
 succeeded = example.run(instance, reporter)
 RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
 succeeded
 end.all?
 end
  52. Running Examples def self.run_examples(reporter)
 ordering_strategy.order(filtered_examples).map do |example|
 next if RSpec.world.wants_to_quit


    instance = new(example.inspect_output)
 set_ivars(instance, before_context_ivars)
 succeeded = example.run(instance, reporter)
 RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
 succeeded
 end.all?
 end
  53. Running Examples def self.run_examples(reporter)
 ordering_strategy.order(filtered_examples).map do |example|
 next if RSpec.world.wants_to_quit


    instance = new(example.inspect_output)
 set_ivars(instance, before_context_ivars)
 succeeded = example.run(instance, reporter)
 RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
 succeeded
 end.all?
 end
  54. Running Examples def self.run_examples(reporter)
 ordering_strategy.order(filtered_examples).map do |example|
 next if RSpec.world.wants_to_quit


    instance = new(example.inspect_output)
 set_ivars(instance, before_context_ivars)
 succeeded = example.run(instance, reporter)
 RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
 succeeded
 end.all?
 end
  55. Running Examples def self.run_examples(reporter)
 ordering_strategy.order(filtered_examples).map do |example|
 next if RSpec.world.wants_to_quit


    instance = new(example.inspect_output)
 set_ivars(instance, before_context_ivars)
 succeeded = example.run(instance, reporter)
 RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
 succeeded
 end.all?
 end
  56. Running One Example • Check that it is not pending

    • Run before blocks • instance_exec the block in the context of the example group • Raise is the example skips or fails • Run after blocks
  57. begin
 run_before_example
 @example_group_instance.instance_exec(self, &@example_block)
 if pending?
 Pending.mark_fixed! self
 raise Pending::PendingExampleFixedError,


    'Expected example to fail since it is pending, but it passed.',
 [location]
 end
 rescue Pending::SkipDeclaredInExample
 # no-op, required metadata has already been set by the `skip`
 # method.
 rescue Exception => e
 set_exception(e)
 ensure
 run_after_example
 finish(reporter)
 end
  58. begin
 run_before_example
 @example_group_instance.instance_exec(self, &@example_block)
 if pending?
 Pending.mark_fixed! self
 raise Pending::PendingExampleFixedError,


    'Expected example to fail since it is pending, but it passed.',
 [location]
 end
 rescue Pending::SkipDeclaredInExample
 # no-op, required metadata has already been set by the `skip`
 # method.
 rescue Exception => e
 set_exception(e)
 ensure
 run_after_example
 finish(reporter)
 end
  59. begin
 run_before_example
 @example_group_instance.instance_exec(self, &@example_block)
 if pending?
 Pending.mark_fixed! self
 raise Pending::PendingExampleFixedError,


    'Expected example to fail since it is pending, but it passed.',
 [location]
 end
 rescue Pending::SkipDeclaredInExample
 # no-op, required metadata has already been set by the `skip`
 # method.
 rescue Exception => e
 set_exception(e)
 ensure
 run_after_example
 finish(reporter)
 end
  60. begin
 run_before_example
 @example_group_instance.instance_exec(self, &@example_block)
 if pending?
 Pending.mark_fixed! self
 raise Pending::PendingExampleFixedError,


    'Expected example to fail since it is pending, but it passed.',
 [location]
 end
 rescue Pending::SkipDeclaredInExample
 # no-op, required metadata has already been set by the `skip`
 # method.
 rescue Exception => e
 set_exception(e)
 ensure
 run_after_example
 finish(reporter)
 end
  61. begin
 run_before_example
 @example_group_instance.instance_exec(self, &@example_block)
 if pending?
 Pending.mark_fixed! self
 raise Pending::PendingExampleFixedError,


    'Expected example to fail since it is pending, but it passed.',
 [location]
 end
 rescue Pending::SkipDeclaredInExample
 # no-op, required metadata has already been set by the `skip`
 # method.
 rescue Exception => e
 set_exception(e)
 ensure
 run_after_example
 finish(reporter)
 end
  62. begin
 run_before_example
 @example_group_instance.instance_exec(self, &@example_block)
 if pending?
 Pending.mark_fixed! self
 raise Pending::PendingExampleFixedError,


    'Expected example to fail since it is pending, but it passed.',
 [location]
 end
 rescue Pending::SkipDeclaredInExample
 # no-op, required metadata has already been set by the `skip`
 # method.
 rescue Exception => e
 set_exception(e)
 ensure
 run_after_example
 finish(reporter)
 end
  63. Expectations and Matchers

  64. expect(name.sort_name).to eq("Rappin, Noel”) ExpectationTarget.new(name.sortName)


  65. An Expectation target responds to “to” and “not_to”

  66. Which take a matcher as an argument and evaluate the

    matcher
  67. A matcher responds to “matches?”

  68. eq is a built in method of RSpec::Matchers def eq(expected)


    BuiltIn::Eq.new(expected)
 end
  69. <ExpectationTarget#"Rappin, Noel">.to(Eq#"Rappin, Noel")

  70. <ExpectationTarget#"Rappin, Noel">.to(Eq#"Rappin, Noel”) PositiveExpectationHandler.handle_matcher(“Rappin, Noel”, <EQ> def match(expected, actual)
 actual

    == expected
 end
  71. Let’s look at a fancier trick expect(Name.new("Noel Rappin")).to be_valid

  72. BE_PREDICATE_REGEX = /^(be_(?:an?_)?)(.*)/
 HAS_REGEX = /^(?:have_)(.*)/
 
 def method_missing(method, *args,

    &block)
 case method.to_s
 when BE_PREDICATE_REGEX
 BuiltIn::BePredicate.new(method, *args, &block)
 when HAS_REGEX
 BuiltIn::Has.new(method, *args, &block)
 else
 super
 end
 end
  73. class BePredicate < BaseMatcher
 include BeHelpers
 
 def initialize(*args, &block)


    @expected = parse_expected(args.shift)
 @args = args
 @block = block
 end
 
 def matches?(actual, &block)
 @actual = actual
 @block ||= block
 predicate_accessible? && predicate_matches?
 end
 
 def predicate_accessible?
 actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
 end
 
 def predicate_matches?
 method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
 @predicate_matches = actual.__send__(method_name, *@args, &@block)
 end
 end
  74. 
 
 def matches?(actual, &block)
 @actual = actual
 @block ||=

    block
 predicate_accessible? && predicate_matches?
 end
 
 def predicate_accessible?
 actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
 end
 
 def predicate_matches?
 method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
 @predicate_matches = actual.__send__(method_name, *@args, &@block)
 end
  75. 
 
 def matches?(actual, &block)
 @actual = actual
 @block ||=

    block
 predicate_accessible? && predicate_matches?
 end
 
 def predicate_accessible?
 actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
 end
 
 def predicate_matches?
 method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
 @predicate_matches = actual.__send__(method_name, *@args, &@block)
 end
  76. 
 
 def matches?(actual, &block)
 @actual = actual
 @block ||=

    block
 predicate_accessible? && predicate_matches?
 end
 
 def predicate_accessible?
 actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
 end
 
 def predicate_matches?
 method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
 @predicate_matches = actual.__send__(method_name, *@args, &@block)
 end
  77. 
 
 def matches?(actual, &block)
 @actual = actual
 @block ||=

    block
 predicate_accessible? && predicate_matches?
 end
 
 def predicate_accessible?
 actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
 end
 
 def predicate_matches?
 method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
 @predicate_matches = actual.__send__(method_name, *@args, &@block)
 end
  78. Emoji

  79. define_example_group_method :example_group
 define_example_group_method :describe
 define_example_group_method :context
 define_example_group_method :xdescribe,
 :skip =>

    "Temporarily skipped with xdescribe"
 define_example_group_method :xcontext,
 :skip => "Temporarily skipped with xcontext"
 define_example_group_method :fdescribe,
 :focus => true
 define_example_group_method :fcontext,
 :focus => true
  80. define_example_method :specify
 define_example_method :focus, :focus => true
 define_example_method :fexample, :focus

    => true
 define_example_method :fit, :focus => true
 define_example_method :fspecify, :focus => true
 define_example_method :xexample, :skip => 'Temporarily skipped with xexample'
 define_example_method :xit, :skip => 'Temporarily skipped with xit'
 define_example_method :skip, :skip => true
 define_example_method :pending, :pending => true
  81. require 'rails_helper'
 
 module RSpec
 module Core
 class ExampleGroup
 define_example_group_method

    :!
 define_example_method :" end
 end
 
 module Matchers
 def #(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block)
 ::RSpec::Expectations::ExpectationTarget.for(value, block)
 end
 alias_matcher :❤, :eq
 end
 end
  82. And then… RSpec.! Hotel do
 " "works" do
 #(1 +

    1).to ❤(2)
 end
 end
  83. Mocks

  84. Simple Mock Example require 'rails_helper'
 
 RSpec.describe Name do
 it

    "assigns a rating category" do
 user = User.new()
 expect(user).to receive(:credit_rating).and_return(1000)
 expect(user.rating_category).to eq("approved")
 end
 end
  85. What would this mock need to do? •Ensure the method

    is not called •Track how often it’s called •Verify that expectations are met
  86. Ruby Method Lookup path • Singleton class • instance methods

    of class • Included modules • parent of class • parent’s included modules • and so on…
  87. Singleton Class class << x def foo #something end end

    def x.bar #something end x.foo; x.bar
  88. Key Concepts • Receive is a matcher • Space •

    Proxy • Method Double Original Object Singleton Class MethodDouble Proxy Space Receive Matcher
  89. Receive creates a matcher def receive(method_name, &block)
 Matchers::Receive.new(method_name, block)
 end

  90. Inside the Receive Matcher def setup_expectation(subject, &block)
 warn_if_any_instance("expect", subject)
 @describable

    = setup_mock_proxy_method_substitute( subject, :add_message_expectation, block)
 end
 alias matches? setup_expectation
 
 def setup_mock_proxy_method_substitute(subject, method, block)
 proxy = ::RSpec::Mocks.space.proxy_for(subject)
 setup_method_substitute(proxy, method, block)
 end
  91. Inside the Receive Matcher def setup_expectation(subject, &block)
 warn_if_any_instance("expect", subject)
 @describable

    = setup_mock_proxy_method_substitute( subject, :add_message_expectation, block)
 end
 alias matches? setup_expectation
 
 def setup_mock_proxy_method_substitute(subject, method, block)
 proxy = ::RSpec::Mocks.space.proxy_for(subject)
 setup_method_substitute(proxy, method, block)
 end
  92. Inside the Receive Matcher def setup_expectation(subject, &block)
 warn_if_any_instance("expect", subject)
 @describable

    = setup_mock_proxy_method_substitute( subject, :add_message_expectation, block)
 end
 alias matches? setup_expectation
 
 def setup_mock_proxy_method_substitute(subject, method, block)
 proxy = ::RSpec::Mocks.space.proxy_for(subject)
 setup_method_substitute(proxy, method, block)
 end
  93. Inside the Receive Matcher def setup_expectation(subject, &block)
 warn_if_any_instance("expect", subject)
 @describable

    = setup_mock_proxy_method_substitute( subject, :add_message_expectation, block)
 end
 alias matches? setup_expectation
 
 def setup_mock_proxy_method_substitute(subject, method, block)
 proxy = ::RSpec::Mocks.space.proxy_for(subject)
 setup_method_substitute(proxy, method, block)
 end
  94. Proxy •Substitutes the doubled method •Tracks calls

  95. Proxy adds Expectations def add_message_expectation(method_name, opts={}, &block)
 location = opts.fetch(:expected_from)

    { CallerFilter.first_non_rspec_line }
 meth_double = method_double_for(method_name)
 
 if null_object? && !block
 meth_double.add_default_stub(@error_generator, @order_group, location, opts) do
 @object
 end
 end
 
 meth_double.add_expectation @error_generator, @order_group, location, opts, &block
 end
  96. Proxy adds Expectations def add_message_expectation(method_name, opts={}, &block)
 location = opts.fetch(:expected_from)

    { CallerFilter.first_non_rspec_line }
 meth_double = method_double_for(method_name)
 
 if null_object? && !block
 meth_double.add_default_stub(@error_generator, @order_group, location, opts) do
 @object
 end
 end
 
 meth_double.add_expectation @error_generator, @order_group, location, opts, &block
 end
  97. Proxy adds Expectations def add_message_expectation(method_name, opts={}, &block)
 location = opts.fetch(:expected_from)

    { CallerFilter.first_non_rspec_line }
 meth_double = method_double_for(method_name)
 
 if null_object? && !block
 meth_double.add_default_stub(@error_generator, @order_group, location, opts) do
 @object
 end
 end
 
 meth_double.add_expectation @error_generator, @order_group, location, opts, &block
 end
  98. Method Double def define_proxy_method
 return if @method_is_proxied
 save_original_method!
 definition_target.class_exec( self,

    method_name, visibility) do |method_double, method_name, visibility|
 define_method(method_name) do |*args, &block|
 method_double.proxy_method_invoked(self, *args, &block)
 end
 __send__(visibility, method_name)
 end
 @method_is_proxied = true
 end
 
 def proxy_method_invoked(_obj, *args, &block)
 @proxy.message_received method_name, *args, &block
 end
  99. Method Double def define_proxy_method
 return if @method_is_proxied
 save_original_method!
 definition_target.class_exec( self,

    method_name, visibility) do |method_double, method_name, visibility|
 define_method(method_name) do |*args, &block|
 method_double.proxy_method_invoked(self, *args, &block)
 end
 __send__(visibility, method_name)
 end
 @method_is_proxied = true
 end
 
 def proxy_method_invoked(_obj, *args, &block)
 @proxy.message_received method_name, *args, &block
 end
  100. Method Double def define_proxy_method
 return if @method_is_proxied
 save_original_method!
 definition_target.class_exec( self,

    method_name, visibility) do |method_double, method_name, visibility|
 define_method(method_name) do |*args, &block|
 method_double.proxy_method_invoked(self, *args, &block)
 end
 __send__(visibility, method_name)
 end
 @method_is_proxied = true
 end
 
 def proxy_method_invoked(_obj, *args, &block)
 @proxy.message_received method_name, *args, &block
 end
  101. Method Double def define_proxy_method
 return if @method_is_proxied
 save_original_method!
 definition_target.class_exec( self,

    method_name, visibility) do |method_double, method_name, visibility|
 define_method(method_name) do |*args, &block|
 method_double.proxy_method_invoked(self, *args, &block)
 end
 __send__(visibility, method_name)
 end
 @method_is_proxied = true
 end
 
 def proxy_method_invoked(_obj, *args, &block)
 @proxy.message_received method_name, *args, &block
 end
  102. Method Double def define_proxy_method
 return if @method_is_proxied
 save_original_method!
 definition_target.class_exec( self,

    method_name, visibility) do |method_double, method_name, visibility|
 define_method(method_name) do |*args, &block|
 method_double.proxy_method_invoked(self, *args, &block)
 end
 __send__(visibility, method_name)
 end
 @method_is_proxied = true
 end
 
 def proxy_method_invoked(_obj, *args, &block)
 @proxy.message_received method_name, *args, &block
 end
  103. Expectations are verified after an example is torn down

  104. See also http://rspec.info/documentation/

  105. Noel Rappin Table XI @noelrap http://www.pragprog.com/book/nrtest2 (test2rappin for 25% off)

    http://www.noelrappin.com/trdd http://www.noelrappin.com/mstwjs