Search code examples
ruby-on-railsrubyrspec-rails

what does this line of code "expect { |b| 5.tap(&b) }.to yield_control" implementing functionally?


Going through the rspec docs i found these lines of code in yielding section of this page http://rubydoc.info/gems/rspec-expectations/frames

Can anyone explain what each line of code in the yielding section does step-by-step


Solution

  • What does expect { |b| 5.tap(&b) }.to yield_control do?

    It is expecting that the code given in the block invokes yield statement.

    The code given in the block (viz. { |b| 5.tap(&b) }) calls the ruby 1.9 tap method which has a yield statement within it's implementation.

    So the statement is effectively asserting that ruby 1.9's tap method has a yield statement. :-)

    To better understand this statement, try the following code example:

    order.rb file

    class Order
    end
    

    order_spec.rb file

    require 'rspec-expectations'
    require './order'
    
    describe Order do
      it "should yield regardless of yielded args" do
        expect { |b| 5.tap(&b);puts "The value of b is #{b.inspect}" }.to yield_control
      end
    end 
    

    Output from executing the spec:

    $ rspec yield_spec.rb
    
    Order
    The value of b is #<RSpec::Matchers::BuiltIn::YieldProbe:0x000001008745f0 @used=true, @num_yields=1, @yielded_args=[[5]]>
      should yield regardless of yielded args
    
    Finished in 0.01028 seconds
    1 example, 0 failures
    

    To understand the other lines in the yielding section, similarly include the expect statements in the spec file as follows:

     it "should yield with no args" do
        expect { |b| Order.yield_if_true(true, &b) }.to yield_with_no_args
      end 
          it "should yield with 5 args" do
        expect { |b| 5.tap(&b) }.to yield_with_args(5)
      end 
    
      it "should yield with integer args" do
        expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum)
      end 
    
      it "should yield with string args" do        
        expect { |b| "a string".tap(&b) }.to yield_with_args(/str/)
      end 
    
      it "should yield 1,2,3 successively" do
        expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
      end 
    
      it "should yield ([:a,1], [:b,2]) successively" do
        expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2]) 
      end 
    

    Note: The yield_if_true method used in the second line seems to be removed from where it was originally defined; I got it go work by adding it into the Order class as follows:

    class Order
      def self.yield_if_true(flag)
        yield if flag
      end
    end