require 'spec_helper' module YieldHelpers # these helpers are prefixed with an underscore to prevent # collisions with the matchers (some of which have the same names) def _dont_yield end def _yield_with_no_args yield end def _yield_with_args(*args) yield(*args) end end class InstanceEvaler include RSpec::Matchers::Extensions::InstanceEvalWithArgs def yield_with_no_args(&block) instance_eval_with_args(&block) end def yield_with_args(*args, &block) instance_eval_with_args(*args, &block) end def each_arg(*args, &block) args.each do |arg| instance_eval_with_args(arg, &block) end end end describe "yield_control matcher" do include YieldHelpers extend YieldHelpers it_behaves_like "an RSpec matcher", :valid_value => lambda { |b| _yield_with_no_args(&b) }, :invalid_value => lambda { |b| _dont_yield(&b) } do let(:matcher) { yield_control } end it 'has a description' do expect(yield_control.description).to eq("yield control") end describe "expect {...}.to yield_control" do it 'passes if the block yields, regardless of the number of yielded arguments' do expect { |b| _yield_with_no_args(&b) }.to yield_control expect { |b| _yield_with_args(1, 2, &b) }.to yield_control end it 'passes if the block yields using instance_eval' do expect { |b| InstanceEvaler.new.yield_with_no_args(&b) }.to yield_control end it 'fails if the block does not yield' do expect { expect { |b| _dont_yield(&b) }.to yield_control }.to fail_with(/expected given block to yield control/) end context "with exact count" do it 'fails if the block yields wrong number of times' do expect { expect { |b| [1, 2, 3].each(&b) }.to yield_control.twice }.to fail_with(/expected given block to yield control twice/) expect { expect { |b| [1, 2].each(&b) }.to yield_control.exactly(3).times }.to fail_with(/expected given block to yield control 3 times/) end it 'passes if the block yields the specified number of times' do expect { |b| [1].each(&b) }.to yield_control.once expect { |b| [1, 2].each(&b) }.to yield_control.twice expect { |b| [1, 2, 3].each(&b) }.to yield_control.exactly(3).times end end context "with at_least count" do it 'passes if the block yields the given number of times' do expect { |b| [1, 2].each(&b) }.to yield_control.at_least(2).times expect { |b| [1, 2, 3].each(&b) }.to yield_control.at_least(3).times end it 'passes if the block yields more times' do expect { |b| [1, 2, 3].each(&b) }.to yield_control.at_least(2).times expect { |b| [1, 2, 3, 4].each(&b) }.to yield_control.at_least(3).times end it 'allows :once and :twice to be passed as counts' do expect { |b| [1].each(&b) }.to yield_control.at_least(:once) expect { |b| [1, 2].each(&b) }.to yield_control.at_least(:once) expect { expect { |b| [].each(&b) }.to yield_control.at_least(:once) }.to fail_with(/at least once/) expect { |b| [1, 2].each(&b) }.to yield_control.at_least(:twice) expect { |b| [1, 2, 3].each(&b) }.to yield_control.at_least(:twice) expect { expect { |b| [1].each(&b) }.to yield_control.at_least(:twice) }.to fail_with(/at least twice/) end it 'fails if the block yields too few times' do expect { expect { |b| _yield_with_no_args(&b) }.to yield_control.at_least(2).times }.to fail_with(/expected given block to yield control at least twice/) end end context "with at_most count" do it 'passes if the block yields the given number of times' do expect { |b| [1, 2].each(&b) }.to yield_control.at_most(2).times expect { |b| [1, 2, 3].each(&b) }.to yield_control.at_most(3).times end it 'passes if the block yields fewer times' do expect { |b| [1, 2].each(&b) }.to yield_control.at_most(3).times end it 'allows :once and :twice to be passed as counts' do expect { |b| [1].each(&b) }.to yield_control.at_most(:once) expect { expect { |b| [1, 2].each(&b) }.to yield_control.at_most(:once) }.to fail_with(/expected given block to yield control at most once/) expect { |b| [1, 2].each(&b) }.to yield_control.at_most(:twice) expect { expect { |b| [1, 2, 3].each(&b) }.to yield_control.at_most(:twice) }.to fail_with(/expected given block to yield control at most twice/) end it 'fails if the block yields too many times' do expect { expect { |b| [1, 2, 3].each(&b) }.to yield_control.at_most(2).times }.to fail_with(/expected given block to yield control at most twice/) end end end describe "expect {...}.not_to yield_control" do it 'passes if the block does not yield' do expect { |b| _dont_yield(&b) }.not_to yield_control end it 'fails if the block does yield' do expect { expect { |b| _yield_with_no_args(&b) }.not_to yield_control }.to fail_with(/expected given block not to yield control/) end it 'fails if the expect block does not accept an argument' do expect { expect { }.not_to yield_control }.to raise_error(/expect block must accept an argument/) end it 'raises an error if the expect block arg is not passed to a method as a block' do expect { expect { |b| }.not_to yield_control }.to raise_error(/must pass the argument.*as a block/) end end end describe "yield_with_no_args matcher" do include YieldHelpers extend YieldHelpers it_behaves_like "an RSpec matcher", :valid_value => lambda { |b| _yield_with_no_args(&b) }, :invalid_value => lambda { |b| _dont_yield(&b) } do let(:matcher) { yield_with_no_args } end it 'has a description' do expect(yield_with_no_args.description).to eq("yield with no args") end describe "expect {...}.to yield_with_no_args" do it 'passes if the block yields with no args' do expect { |b| _yield_with_no_args(&b) }.to yield_with_no_args end it 'passes if the block yields with no args using instance_eval' do expect { |b| InstanceEvaler.new.yield_with_no_args(&b) }.to yield_with_no_args end it 'fails if the block does not yield' do expect { expect { |b| _dont_yield(&b) }.to yield_with_no_args }.to fail_with(/expected given block to yield with no arguments, but did not yield/) end it 'fails if the block yields with args' do expect { expect { |b| _yield_with_args(1, &b) }.to yield_with_no_args }.to fail_with(/expected given block to yield with no arguments, but yielded with arguments/) end it 'fails if the block yields with arg false' do expect { expect { |b| _yield_with_args(false, &b) }.to yield_with_no_args }.to fail_with(/expected given block to yield with no arguments, but yielded with arguments/) end it 'raises an error if it yields multiple times' do expect { expect { |b| [1, 2].each(&b) }.to yield_with_no_args }.to raise_error(/not designed.*yields multiple times/) end end describe "expect {...}.not_to yield_with_no_args" do it "passes if the block does not yield" do expect { |b| _dont_yield(&b) }.not_to yield_with_no_args end it "passes if the block yields with args" do expect { |b| _yield_with_args(1, &b) }.not_to yield_with_no_args end it "fails if the block yields with no args" do expect { expect { |b| _yield_with_no_args(&b) }.not_to yield_with_no_args }.to fail_with(/expected given block not to yield with no arguments, but did/) end it 'fails if the expect block does not accept an argument' do expect { expect { }.not_to yield_with_no_args }.to raise_error(/expect block must accept an argument/) end it 'raises an error if the expect block arg is not passed to a method as a block' do expect { expect { |b| }.not_to yield_with_no_args }.to raise_error(/must pass the argument.*as a block/) end end end describe "yield_with_args matcher" do include YieldHelpers extend YieldHelpers it_behaves_like "an RSpec matcher", :valid_value => lambda { |b| _yield_with_args(1, &b) }, :invalid_value => lambda { |b| _dont_yield(&b) } do let(:matcher) { yield_with_args } end it 'has a description' do expect(yield_with_args.description).to eq("yield with args") expect(yield_with_args(1, 3).description).to eq("yield with args(1, 3)") expect(yield_with_args(false).description).to eq("yield with args(false)") end describe "expect {...}.to yield_with_args" do it 'passes if the block yields with arguments' do expect { |b| _yield_with_args(1, &b) }.to yield_with_args end it 'fails if the block does not yield' do expect { expect { |b| _dont_yield(&b) }.to yield_with_args }.to fail_with(/expected given block to yield with arguments, but did not yield/) end it 'fails if the block yields with no arguments' do expect { expect { |b| _yield_with_no_args(&b) }.to yield_with_args }.to fail_with(/expected given block to yield with arguments, but yielded with no arguments/) end it 'raises an error if it yields multiple times' do expect { expect { |b| [1, 2].each(&b) }.to yield_with_args }.to raise_error(/not designed.*yields multiple times/) end end describe "expect {...}.not_to yield_with_args" do it 'fails if the block yields with arguments' do expect { expect { |b| _yield_with_args(1, &b) }.not_to yield_with_args }.to fail_with(/expected given block not to yield with arguments, but did/) end it 'passes if the block does not yield' do expect { |b| _dont_yield(&b) }.not_to yield_with_args end it 'passes if the block yields with no arguments' do expect { |b| _yield_with_no_args(&b) }.not_to yield_with_args end it 'fails if the expect block does not accept an argument' do expect { expect { }.not_to yield_with_args }.to raise_error(/expect block must accept an argument/) end it 'raises an error if the expect block arg is not passed to a method as a block' do expect { expect { |b| }.not_to yield_with_args }.to raise_error(/must pass the argument.*as a block/) end end describe "expect {...}.to yield_with_args(3, 17)" do it 'passes if the block yields with the given arguments' do expect { |b| _yield_with_args(3, 17, &b) }.to yield_with_args(3, 17) end it 'passes if the block yields with the given arguments using instance_eval' do expect { |b| InstanceEvaler.new.yield_with_args(3, 17, &b) }.to yield_with_args(3, 17) end it 'fails if the block does not yield' do expect { expect { |b| _dont_yield(&b) }.to yield_with_args(3, 17) }.to fail_with(/expected given block to yield with arguments, but did not yield/) end it 'fails if the block yields with no arguments' do expect { expect { |b| _yield_with_no_args(&b) }.to yield_with_args(3, 17) }.to fail_with(/expected given block to yield with arguments, but yielded with unexpected arguments/) end it 'fails if the block yields with different arguments' do expect { expect { |b| _yield_with_args("a", "b", &b) }.to yield_with_args("a", "c") }.to fail_with(/expected given block to yield with arguments, but yielded with unexpected arguments/) end end describe "expect {...}.not_to yield_with_args(3, 17)" do it 'passes if the block yields with different arguments' do expect { |b| _yield_with_args("a", "b", &b) }.not_to yield_with_args("a", "c") end it 'fails if the block yields with the given arguments' do expect { expect { |b| _yield_with_args("a", "b", &b) }.not_to yield_with_args("a", "b") }.to fail_with(/expected given block not to yield with arguments, but yielded with expected arguments/) end end describe "expect {...}.to yield_with_args( false )" do it 'passes if the block yields with the given arguments' do expect { |b| _yield_with_args(false, &b) }.to yield_with_args(false) end it 'passes if the block yields with the given arguments using instance_eval' do expect { |b| InstanceEvaler.new.yield_with_args(false, &b) }.to yield_with_args(false) end it 'fails if the block does not yield' do expect { expect { |b| _dont_yield(&b) }.to yield_with_args(false) }.to fail_with(/expected given block to yield with arguments, but did not yield/) end it 'fails if the block yields with no arguments' do expect { expect { |b| _yield_with_no_args(&b) }.to yield_with_args(false) }.to fail_with(/expected given block to yield with arguments, but yielded with unexpected arguments/) end it 'fails if the block yields with different arguments' do expect { expect { |b| _yield_with_args(false, &b) }.to yield_with_args(true) }.to fail_with(/expected given block to yield with arguments, but yielded with unexpected arguments/) end end describe "expect {...}.to yield_with_args(/reg/, /ex/)" do it "passes if the block yields strings matching the regexes" do expect { |b| _yield_with_args("regular", "expression", &b) }.to yield_with_args(/reg/, /ex/) end it "fails if the block yields strings that do not match the regexes" do expect { expect { |b| _yield_with_args("no", "match", &b) }.to yield_with_args(/reg/, /ex/) }.to fail_with(/expected given block to yield with arguments, but yielded with unexpected arguments/) end end describe "expect {...}.to yield_with_args(String, Fixnum)" do it "passes if the block yields objects of the given types" do expect { |b| _yield_with_args("string", 15, &b) }.to yield_with_args(String, Fixnum) end it "passes if the block yields the given types" do expect { |b| _yield_with_args(String, Fixnum, &b) }.to yield_with_args(String, Fixnum) end it "fails if the block yields objects of different types" do expect { expect { |b| _yield_with_args(15, "string", &b) }.to yield_with_args(String, Fixnum) }.to fail_with(/expected given block to yield with arguments, but yielded with unexpected arguments/) end end end describe "yield_successive_args matcher" do include YieldHelpers extend YieldHelpers it_behaves_like "an RSpec matcher", :valid_value => lambda { |b| [1, 2].each(&b) }, :invalid_value => lambda { |b| _dont_yield(&b) } do let(:matcher) { yield_successive_args(1, 2) } end it 'has a description' do expect(yield_successive_args(1, 3).description).to eq("yield successive args(1, 3)") expect(yield_successive_args([:a, 1], [:b, 2]).description).to eq("yield successive args([:a, 1], [:b, 2])") end describe "expect {...}.to yield_successive_args([:a, 1], [:b, 2])" do it 'passes when the block successively yields the given args' do expect { |b| [ [:a, 1], [:b, 2] ].each(&b) }.to yield_successive_args([:a, 1], [:b, 2]) end it 'fails when the block does not yield that many times' do expect { expect { |b| [[:a, 1]].each(&b) }.to yield_successive_args([:a, 1], [:b, 2]) }.to fail_with(/but yielded with unexpected arguments/) end it 'fails when the block yields the right number of times but with different arguments' do expect { expect { |b| [ [:a, 1], [:b, 3] ].each(&b) }.to yield_successive_args([:a, 1], [:b, 2]) }.to fail_with(/but yielded with unexpected arguments/) end end describe "expect {...}.to yield_successive_args(1, 2, 3)" do it 'passes when the block successively yields the given args' do expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3) end it 'passes when the block successively yields the given args using instance_eval' do expect { |b| InstanceEvaler.new.each_arg(1, 2, 3, &b) }.to yield_successive_args(1, 2, 3) end it 'fails when the block does not yield the expected args' do expect { expect { |b| [1, 2, 4].each(&b) }.to yield_successive_args([:a, 1], [:b, 2]) }.to fail_with(/but yielded with unexpected arguments/) end end describe "expect {...}.not_to yield_successive_args(1, 2, 3)" do it 'passes when the block does not yield' do expect { |b| _dont_yield(&b) }.not_to yield_successive_args(1, 2, 3) end it 'passes when the block yields the wrong number of times' do expect { |b| [1, 2].each(&b) }.not_to yield_successive_args(1, 2, 3) end it 'passes when the block yields the wrong arguments' do expect { |b| [1, 2, 4].each(&b) }.not_to yield_successive_args(1, 2, 3) end it 'fails when the block yields the given arguments' do expect { expect { |b| [1, 2, 3].each(&b) }.not_to yield_successive_args(1, 2, 3) }.to fail_with(/expected given block not to yield successively/) end it 'fails if the expect block does not accept an argument' do expect { expect { }.not_to yield_successive_args(1, 2, 3) }.to raise_error(/expect block must accept an argument/) end it 'raises an error if the expect block arg is not passed to a method as a block' do expect { expect { |b| }.not_to yield_successive_args(1, 2, 3) }.to raise_error(/must pass the argument.*as a block/) end end describe "expect {...}.to yield_successive_args(String, Fixnum)" do it "passes if the block successively yields objects of the given types" do expect { |b| ["string", 15].each(&b) }.to yield_successive_args(String, Fixnum) end it "passes if the block yields the given types" do expect { |b| [String, Fixnum].each(&b) }.to yield_successive_args(String, Fixnum) end it "fails if the block yields objects of different types" do expect { expect { |b| [15, "string"].each(&b) }.to yield_successive_args(String, Fixnum) }.to fail_with(/expected given block to yield successively with arguments/) end end end