Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #129 from rspec/yield_matchers

Yield matchers
  • Loading branch information...
commit e610e40f65adfa1da03cc6326050eaa99dcff45d 2 parents 5bdf4b8 + d020cfe
@myronmarston myronmarston authored
View
15 README.md
@@ -112,6 +112,21 @@ expect { ... }.to throw_symbol(:symbol)
expect { ... }.to throw_symbol(:symbol, 'value')
```
+### Yielding
+
+```ruby
+expect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args
+
+expect { |b| yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded
+
+expect { |b| 5.tap(&b) }.to yield_with_args(5)
+expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum)
+expect { |b| "a string".tap(&b) }.to yield_with_args(/str/)
+
+expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
+expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])
+```
+
### Predicate matchers
```ruby
View
1  features/.nav
@@ -19,6 +19,7 @@
- satisfy.feature
- throw_symbol.feature
- types.feature
+ - yield.feature
- custom_matchers:
- define_matcher.feature
- define_diffable_matcher.feature
View
146 features/built_in_matchers/yield.feature
@@ -0,0 +1,146 @@
+Feature: yield matchers
+
+ There are four related matchers that allow you to specify whether
+ or not a method yields, how many times it yields, whether or not
+ it yields with arguments, and what those arguments are.
+
+ * `yield_control` matches if the method-under-test yields, regardless
+ of whether or not arguments are yielded.
+ * `yield_with_args` matches if the method-under-test yields with
+ arguments. If arguments are provided to this matcher, it will
+ only pass if the actual yielded arguments match the expected ones
+ using `===` or `==`.
+ * `yield_with_no_args` matches if the method-under-test yields with
+ no arguments.
+ * `yield_successive_args` is designed for iterators, and will match
+ if the method-under-test yields the same number of times as arguments
+ passed to this matcher, and all actual yielded arguments match the
+ expected ones using `===` or `==`.
+
+ Note: your expect block _must_ accept an argument that is then passed on to
+ the method-under-test as a block. This acts as a "probe" that allows the matcher
+ to detect whether or not your method yields, and, if so, how many times and what
+ the yielded arguments are.
+
+ Background:
+ Given a file named "my_class.rb" with:
+ """
+ class MyClass
+ def self.yield_once_with(*args)
+ yield *args
+ end
+
+ def self.raw_yield
+ yield
+ end
+
+ def self.dont_yield
+ end
+ end
+ """
+
+ Scenario: yield_control matcher
+ Given a file named "yield_control_spec.rb" with:
+ """
+ require './my_class'
+
+ describe "yield_control matcher" do
+ specify { expect { |b| MyClass.yield_once_with(1, &b) }.to yield_control }
+ specify { expect { |b| MyClass.dont_yield(&b) }.not_to yield_control }
+
+ # deliberate failures
+ specify { expect { |b| MyClass.yield_once_with(1, &b) }.not_to yield_control }
+ specify { expect { |b| MyClass.dont_yield(&b) }.to yield_control }
+ end
+ """
+ When I run `rspec yield_control_spec.rb`
+ Then the output should contain all of these:
+ | 4 examples, 2 failures |
+ | expected given block to yield control |
+ | expected given block not to yield control |
+
+ Scenario: yield_with_args matcher
+ Given a file named "yield_with_args_spec.rb" with:
+ """
+ require './my_class'
+
+ describe "yield_with_args matcher" do
+ specify { expect { |b| MyClass.yield_once_with("foo", &b) }.to yield_with_args }
+ specify { expect { |b| MyClass.yield_once_with("foo", &b) }.to yield_with_args("foo") }
+ specify { expect { |b| MyClass.yield_once_with("foo", &b) }.to yield_with_args(String) }
+ specify { expect { |b| MyClass.yield_once_with("foo", &b) }.to yield_with_args(/oo/) }
+
+ specify { expect { |b| MyClass.yield_once_with("foo", "bar", &b) }.to yield_with_args("foo", "bar") }
+ specify { expect { |b| MyClass.yield_once_with("foo", "bar", &b) }.to yield_with_args(String, String) }
+ specify { expect { |b| MyClass.yield_once_with("foo", "bar", &b) }.to yield_with_args(/fo/, /ar/) }
+
+ specify { expect { |b| MyClass.yield_once_with("foo", "bar", &b) }.not_to yield_with_args(17, "baz") }
+
+ # deliberate failures
+ specify { expect { |b| MyClass.yield_once_with("foo", &b) }.not_to yield_with_args }
+ specify { expect { |b| MyClass.yield_once_with("foo", &b) }.not_to yield_with_args("foo") }
+ specify { expect { |b| MyClass.yield_once_with("foo", &b) }.not_to yield_with_args(String) }
+ specify { expect { |b| MyClass.yield_once_with("foo", &b) }.not_to yield_with_args(/oo/) }
+ specify { expect { |b| MyClass.yield_once_with("foo", "bar", &b) }.not_to yield_with_args("foo", "bar") }
+ specify { expect { |b| MyClass.yield_once_with("foo", "bar", &b) }.to yield_with_args(17, "baz") }
+ end
+ """
+ When I run `rspec yield_with_args_spec.rb`
+ Then the output should contain all of these:
+ | 14 examples, 6 failures |
+ | expected given block not to yield with arguments, but did |
+ | expected given block not to yield with arguments, but yielded with expected arguments |
+ | expected given block to yield with arguments, but yielded with unexpected arguments |
+
+ Scenario: yield_with_no_args matcher
+ Given a file named "yield_with_no_args_spec.rb" with:
+ """
+ require './my_class'
+
+ describe "yield_with_no_args matcher" do
+ specify { expect { |b| MyClass.raw_yield(&b) }.to yield_with_no_args }
+ specify { expect { |b| MyClass.dont_yield(&b) }.not_to yield_with_no_args }
+ specify { expect { |b| MyClass.yield_once_with("a", &b) }.not_to yield_with_no_args }
+
+ # deliberate failures
+ specify { expect { |b| MyClass.raw_yield(&b) }.not_to yield_with_no_args }
+ specify { expect { |b| MyClass.dont_yield(&b) }.to yield_with_no_args }
+ specify { expect { |b| MyClass.yield_once_with("a", &b) }.to yield_with_no_args }
+ end
+ """
+ When I run `rspec yield_with_no_args_spec.rb`
+ Then the output should contain all of these:
+ | 6 examples, 3 failures |
+ | expected given block not to yield with no arguments, but did |
+ | expected given block to yield with no arguments, but did not yield |
+ | expected given block to yield with no arguments, but yielded with arguments: ["a"] |
+
+ Scenario: yield_successive_args matcher
+ Given a file named "yield_successive_args_spec.rb" with:
+ """
+ def array
+ [1, 2, 3]
+ end
+
+ def array_of_tuples
+ [[:a, :b], [:c, :d]]
+ end
+
+ describe "yield_successive_args matcher" do
+ specify { expect { |b| array.each(&b) }.to yield_successive_args(1, 2, 3) }
+ specify { expect { |b| array_of_tuples.each(&b) }.to yield_successive_args([:a, :b], [:c, :d]) }
+ specify { expect { |b| array.each(&b) }.to yield_successive_args(Fixnum, Fixnum, Fixnum) }
+ specify { expect { |b| array.each(&b) }.not_to yield_successive_args(1, 2) }
+
+ # deliberate failures
+ specify { expect { |b| array.each(&b) }.not_to yield_successive_args(1, 2, 3) }
+ specify { expect { |b| array_of_tuples.each(&b) }.not_to yield_successive_args([:a, :b], [:c, :d]) }
+ specify { expect { |b| array.each(&b) }.not_to yield_successive_args(Fixnum, Fixnum, Fixnum) }
+ specify { expect { |b| array.each(&b) }.to yield_successive_args(1, 2) }
+ end
+ """
+ When I run `rspec yield_successive_args_spec.rb`
+ Then the output should contain all of these:
+ | 8 examples, 4 failures |
+ | expected given block not to yield successively with arguments, but yielded with expected arguments |
+ | expected given block to yield successively with arguments, but yielded with unexpected arguments |
View
82 lib/rspec/matchers.rb
@@ -582,6 +582,88 @@ def throw_symbol(expected_symbol=nil, expected_arg=nil)
BuiltIn::ThrowSymbol.new(expected_symbol, expected_arg)
end
+ # Passes if the method called in the expect block yields, regardless
+ # of whether or not arguments are yielded.
+ #
+ # @example
+ #
+ # expect { |b| 5.tap(&b) }.to yield_control
+ # expect { |b| "a".to_sym(&b) }.not_to yield_control
+ #
+ # @note Your expect block must accept a parameter and pass it on to
+ # the method-under-test as a block.
+ # @note This matcher is not designed for use with methods that yield
+ # multiple times.
+ def yield_control
+ BuiltIn::YieldControl.new
+ end
+
+ # Passes if the method called in the expect block yields with
+ # no arguments. Fails if it does not yield, or yields with arguments.
+ #
+ # @example
+ #
+ # expect { |b| User.transaction(&b) }.to yield_with_no_args
+ # expect { |b| 5.tap(&b) }.not_to yield_with_no_args # because it yields with `5`
+ # expect { |b| "a".to_sym(&b) }.not_to yield_with_no_args # because it does not yield
+ #
+ # @note Your expect block must accept a parameter and pass it on to
+ # the method-under-test as a block.
+ # @note This matcher is not designed for use with methods that yield
+ # multiple times.
+ def yield_with_no_args
+ BuiltIn::YieldWithNoArgs.new
+ end
+
+ # Given no arguments, matches if the method called in the expect
+ # block yields with arguments (regardless of what they are or how
+ # many there are).
+ #
+ # Given arguments, matches if the method called in the expect block
+ # yields with arguments that match the given arguments.
+ #
+ # Argument matching is done using `===` (the case match operator)
+ # and `==`. If the expected and actual arguments match with either
+ # operator, the matcher will pass.
+ #
+ # @example
+ #
+ # expect { |b| 5.tap(&b) }.to yield_with_args # because #tap yields an arg
+ # expect { |b| 5.tap(&b) }.to yield_with_args(5) # because 5 == 5
+ # expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum) # because Fixnum === 5
+ # expect { |b| File.open("f.txt", &b) }.to yield_with_args(/txt/) # because /txt/ === "f.txt"
+ #
+ # expect { |b| User.transaction(&b) }.not_to yield_with_args # because it yields no args
+ # expect { |b| 5.tap(&b) }.not_to yield_with_args(1, 2, 3)
+ #
+ # @note Your expect block must accept a parameter and pass it on to
+ # the method-under-test as a block.
+ # @note This matcher is not designed for use with methods that yield
+ # multiple times.
+ def yield_with_args(*args)
+ BuiltIn::YieldWithArgs.new(*args)
+ end
+
+ # Designed for use with methods that repeatedly yield (such as
+ # iterators). Passes if the method called in the expect block yields
+ # multiple times with arguments matching those given.
+ #
+ # Argument matching is done using `===` (the case match operator)
+ # and `==`. If the expected and actual arguments match with either
+ # operator, the matcher will pass.
+ #
+ # @example
+ #
+ # expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
+ # expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])
+ # expect { |b| [1, 2, 3].each(&b) }.not_to yield_successive_args(1, 2)
+ #
+ # @note Your expect block must accept a parameter and pass it on to
+ # the method-under-test as a block.
+ def yield_successive_args(*args)
+ BuiltIn::YieldSuccessiveArgs.new(*args)
+ end
+
# Passes if actual contains all of the expected regardless of order.
# This works for collections. Pass in multiple args and it will only
# pass if all args are found in collection.
View
4 lib/rspec/matchers/built_in.rb
@@ -28,6 +28,10 @@ module BuiltIn
autoload :EndWith, 'rspec/matchers/built_in/start_and_end_with'
autoload :Satisfy, 'rspec/matchers/built_in/satisfy'
autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol'
+ autoload :YieldControl, 'rspec/matchers/built_in/yield'
+ autoload :YieldWithArgs, 'rspec/matchers/built_in/yield'
+ autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield'
+ autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
end
end
end
View
222 lib/rspec/matchers/built_in/yield.rb
@@ -0,0 +1,222 @@
+module RSpec
+ module Matchers
+ module BuiltIn
+ class YieldProbe
+ def self.probe(block)
+ probe = new
+ assert_valid_expect_block!(block)
+ block.call(probe)
+ probe.assert_used!
+ probe
+ end
+
+ attr_accessor :num_yields, :yielded_args
+
+ def initialize
+ @used = false
+ self.num_yields, self.yielded_args = 0, []
+ end
+
+ def to_proc
+ @used = true
+
+ probe = self
+ Proc.new do |*args|
+ probe.num_yields += 1
+ probe.yielded_args << args
+ end
+ end
+
+ def single_yield_args
+ yielded_args.first
+ end
+
+ def yielded_once?(matcher_name)
+ case num_yields
+ when 1 then true
+ when 0 then false
+ else
+ raise "The #{matcher_name} matcher is not designed to be used with a " +
+ "method that yields multiple times. Use the yield_successive_args " +
+ "matcher for that case."
+ end
+ end
+
+ def successive_yield_args
+ yielded_args.map do |arg_array|
+ arg_array.size == 1 ? arg_array.first : arg_array
+ end
+ end
+
+ def assert_used!
+ return if @used
+ raise "You must pass the argument yielded to your expect block on " +
+ "to the method-under-test as a block. It acts as a probe that " +
+ "allows the matcher to detect whether or not the method-under-test " +
+ "yields, and, if so, how many times, and what the yielded arguments " +
+ "are."
+ end
+
+ def self.assert_valid_expect_block!(block)
+ return if block.arity == 1
+ raise "Your expect block must accept an argument to be used with this " +
+ "matcher. Pass the argument as a block on to the method you are testing."
+ end
+ end
+
+ class YieldControl
+ include BaseMatcher
+
+ def matches?(block)
+ probe = YieldProbe.probe(block)
+ probe.yielded_once?(:yield_control)
+ end
+
+ def failure_message_for_should
+ "expected given block to yield control"
+ end
+
+ def failure_message_for_should_not
+ "expected given block not to yield control"
+ end
+ end
+
+ class YieldWithNoArgs
+ include BaseMatcher
+
+ def matches?(block)
+ @probe = YieldProbe.probe(block)
+ @probe.yielded_once?(:yield_with_no_args) && @probe.single_yield_args.none?
+ end
+
+ def failure_message_for_should
+ "expected given block to yield with no arguments, but #{failure_reason}"
+ end
+
+ def failure_message_for_should_not
+ "expected given block not to yield with no arguments, but did"
+ end
+
+ private
+
+ def failure_reason
+ if @probe.num_yields.zero?
+ "did not yield"
+ else
+ "yielded with arguments: #{@probe.single_yield_args.inspect}"
+ end
+ end
+ end
+
+ class YieldWithArgs
+ def initialize(*args)
+ @expected = args
+ end
+
+ def matches?(block)
+ @probe = YieldProbe.probe(block)
+ @actual = @probe.single_yield_args
+ @probe.yielded_once?(:yield_with_args) && args_match?
+ end
+
+ def failure_message_for_should
+ "expected given block to yield with arguments, but #{positive_failure_reason}"
+ end
+
+ def failure_message_for_should_not
+ "expected given block not to yield with arguments, but #{negative_failure_reason}"
+ end
+
+ def description
+ desc = "yield with args"
+ desc << "(" + @expected.map { |e| e.inspect }.join(", ") + ")" if @expected.any?
+ desc
+ end
+
+ private
+
+ def positive_failure_reason
+ if @probe.num_yields.zero?
+ "did not yield"
+ else
+ @positive_args_failure
+ end
+ end
+
+ def negative_failure_reason
+ if all_args_match?
+ "yielded with expected arguments" +
+ "\nexpected not: #{@expected.inspect}" +
+ "\n got: #{@actual.inspect} (compared using === and ==)"
+ else
+ "did"
+ end
+ end
+
+ def args_match?
+ if @expected.none? # expect {...}.to yield_with_args
+ @positive_args_failure = "yielded with no arguments" if @actual.none?
+ return @actual.any?
+ end
+
+ unless match = all_args_match?
+ @positive_args_failure = "yielded with unexpected arguments" +
+ "\nexpected: #{@expected.inspect}" +
+ "\n got: #{@actual.inspect} (compared using === and ==)"
+ end
+
+ match
+ end
+
+ def all_args_match?
+ return false if @expected.size != @actual.size
+
+ @expected.zip(@actual).all? do |expected, actual|
+ expected === actual || actual == expected
+ end
+ end
+ end
+
+ class YieldSuccessiveArgs
+ def initialize(*args)
+ @expected = args
+ end
+
+ def matches?(block)
+ @probe = YieldProbe.probe(block)
+ @actual = @probe.successive_yield_args
+ args_match?
+ end
+
+ def failure_message_for_should
+ "expected given block to yield successively with arguments, but yielded with unexpected arguments" +
+ "\nexpected: #{@expected.inspect}" +
+ "\n got: #{@actual.inspect} (compared using === and ==)"
+ end
+
+ def failure_message_for_should_not
+ "expected given block not to yield successively with arguments, but yielded with expected arguments" +
+ "\nexpected not: #{@expected.inspect}" +
+ "\n got: #{@actual.inspect} (compared using === and ==)"
+ end
+
+ def description
+ desc = "yield successive args"
+ desc << "(" + @expected.map { |e| e.inspect }.join(", ") + ")"
+ desc
+ end
+
+ private
+
+ def args_match?
+ return false if @expected.size != @actual.size
+
+ @expected.zip(@actual).all? do |expected, actual|
+ expected === actual || actual == expected
+ end
+ end
+ end
+ end
+ end
+end
+
View
374 spec/rspec/matchers/yield_spec.rb
@@ -0,0 +1,374 @@
+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
+
+ it 'has a description' do
+ yield_control.description.should 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
+
+ it 'raises an error if it yields multiple times' do
+ expect {
+ expect { |b| [1, 2].each(&b) }.to yield_control
+ }.to raise_error(/not designed.*yields multiple times/)
+ 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
+
+ it 'has a description' do
+ yield_with_no_args.description.should 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 '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
+
+ it 'has a description' do
+ yield_with_args.description.should eq("yield with args")
+ yield_with_args(1, 3).description.should eq("yield with args(1, 3)")
+ 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(/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
+
+ it 'has a description' do
+ yield_successive_args(1, 3).description.should eq("yield successive args(1, 3)")
+ yield_successive_args([:a, 1], [:b, 2]).description.should 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
+
Please sign in to comment.
Something went wrong with that request. Please try again.