rspec-expectations 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +55 -4
  5. data/README.md +1 -1
  6. data/lib/rspec/expectations.rb +13 -1
  7. data/lib/rspec/expectations/configuration.rb +17 -0
  8. data/lib/rspec/expectations/expectation_target.rb +3 -9
  9. data/lib/rspec/expectations/fail_with.rb +1 -3
  10. data/lib/rspec/expectations/failure_aggregator.rb +194 -0
  11. data/lib/rspec/expectations/minitest_integration.rb +13 -0
  12. data/lib/rspec/expectations/version.rb +1 -1
  13. data/lib/rspec/matchers.rb +59 -5
  14. data/lib/rspec/matchers/built_in/base_matcher.rb +56 -7
  15. data/lib/rspec/matchers/built_in/be.rb +25 -15
  16. data/lib/rspec/matchers/built_in/be_between.rb +1 -1
  17. data/lib/rspec/matchers/built_in/be_within.rb +2 -2
  18. data/lib/rspec/matchers/built_in/contain_exactly.rb +12 -8
  19. data/lib/rspec/matchers/built_in/eq.rb +3 -38
  20. data/lib/rspec/matchers/built_in/eql.rb +2 -2
  21. data/lib/rspec/matchers/built_in/equal.rb +3 -3
  22. data/lib/rspec/matchers/built_in/exist.rb +2 -2
  23. data/lib/rspec/matchers/built_in/has.rb +3 -1
  24. data/lib/rspec/matchers/built_in/have_attributes.rb +5 -4
  25. data/lib/rspec/matchers/built_in/include.rb +44 -19
  26. data/lib/rspec/matchers/built_in/match.rb +9 -1
  27. data/lib/rspec/matchers/built_in/operators.rb +14 -5
  28. data/lib/rspec/matchers/built_in/output.rb +9 -2
  29. data/lib/rspec/matchers/built_in/raise_error.rb +64 -22
  30. data/lib/rspec/matchers/built_in/respond_to.rb +2 -3
  31. data/lib/rspec/matchers/built_in/satisfy.rb +7 -9
  32. data/lib/rspec/matchers/built_in/start_or_end_with.rb +3 -1
  33. data/lib/rspec/matchers/built_in/throw_symbol.rb +1 -1
  34. data/lib/rspec/matchers/built_in/yield.rb +7 -5
  35. data/lib/rspec/matchers/composable.rb +5 -4
  36. data/lib/rspec/matchers/dsl.rb +19 -6
  37. data/lib/rspec/matchers/english_phrasing.rb +42 -0
  38. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +2 -8
  39. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  40. data/lib/rspec/matchers/matcher_delegator.rb +2 -0
  41. metadata +9 -7
  42. metadata.gz.sig +0 -0
  43. data/lib/rspec/matchers/pretty.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3aa831b7183b5d522f5e67b19053832a7adeec29
4
- data.tar.gz: fef8e60148530b9df3545e113341083c426baa56
3
+ metadata.gz: 050cebd3fe559b7cd5391cba39333fc9815e7650
4
+ data.tar.gz: 0034049cdc0f2fd7ab5210ba606b72a267b3abbc
5
5
  SHA512:
6
- metadata.gz: e51ebb3d7564f575eee701467eda6e003fade12c2644b38b6b8eba5d2820bccfe60ab22d8ae410e76bd0e5c18dfdbd94298108699f5d4d6a12ee61fb65f03db1
7
- data.tar.gz: 75f4ea4fd9cf63a31967aa3cceb6071de46dd57c3326fe8cd35d47710c741d45d36ba2b77593236d2f614a0b1e921c6a21e08b60c41cd23b7431e77835d68248
6
+ metadata.gz: afee08bfc1eedff07267817c53989802b649cab426c15f55aa869e71bac7db27eadb679b854d5c9da3bbdb7ac39bedcc7c448e8fd7ae8eec311b1a19a694ff07
7
+ data.tar.gz: 1215a16c5ec70686dba0fd02d4ae14c4238352a995850a4c437eda0d7a4279597e1866f7426c623afc31ba32db5215517102576a2442caeddfdc44da308e5123
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -1,3 +1,54 @@
1
+ ### 3.3.0 / 2015-06-12
2
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.2.1...v3.3.0)
3
+
4
+ Enhancements:
5
+
6
+ * Expose `RSpec::Matchers::EnglishPhrasing` to make it easier to write
7
+ nice failure messages in custom matchers. (Jared Beck, #736)
8
+ * Add `RSpec::Matchers::FailMatchers`, a mixin which provides
9
+ `fail`, `fail_with` and `fail_including` matchers for use in
10
+ specifying that an expectation fails for use by
11
+ extension/plugin authors. (Charlie Rudolph, #729)
12
+ * Avoid loading `tempfile` (and its dependencies) unless
13
+ it is absolutely needed. (Myron Marston, #735)
14
+ * Improve failure output when attempting to use `be_true` or `be_false`.
15
+ (Tim Wade, #744)
16
+ * Define `RSpec::Matchers#respond_to_missing?` so that
17
+ `RSpec::Matchers#respond_to?` and `RSpec::Matchers#method` handle
18
+ dynamic predicate matchers. (Andrei Botalov, #751)
19
+ * Use custom Time/DateTime/BigDecimal formatting for all matchers
20
+ so they are consistently represented in failure messages.
21
+ (Gavin Miller, #740)
22
+ * Add configuration to turn off warnings about matcher combinations that
23
+ may cause false positives. (Jon Rowe, #768)
24
+ * Warn when using a bare `raise_error` matcher that you may be subject to
25
+ false positives. (Jon Rowe, #768)
26
+ * Warn rather than raise when using the`raise_error` matcher in negative
27
+ expectations that may be subject to false positives. (Jon Rowe, #775)
28
+ * Improve failure message for `include(a, b, c)` so that if `a` and `b`
29
+ are included the failure message only mentions `c`. (Chris Arcand, #780)
30
+ * Allow `satisfy` matcher to take an optional description argument
31
+ that will be used in the `description`, `failure_message` and
32
+ `failure_message_when_negated` in place of the undescriptive
33
+ "sastify block". (Chris Arcand, #783)
34
+ * Add new `aggregate_failures` API that allows multiple independent
35
+ expectations to all fail and be listed in the failure output, rather
36
+ than the example aborting on the first failure. (Myron Marston, #776)
37
+ * Improve `raise_error` matcher so that it can accept a matcher as a single argument
38
+ that matches the message. (Time Wade, #782)
39
+
40
+ Bug Fixes:
41
+
42
+ * Make `contain_exactly` / `match_array` work with strict test doubles
43
+ that have not defined `<=>`. (Myron Marston, #758)
44
+ * Fix `include` matcher so that it omits the diff when it would
45
+ confusingly highlight items that are actually included but are not
46
+ an exact match in a line-by-line diff. (Tim Wade, #763)
47
+ * Fix `match` matcher so that it does not blow up when matching a string
48
+ or regex against another matcher (rather than a string or regex).
49
+ (Myron Marston, #772)
50
+ * Silence whitespace-only diffs. (Myron Marston, #801)
51
+
1
52
  ### 3.2.1 / 2015-04-06
2
53
  [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.2.0...v3.2.1)
3
54
 
@@ -321,7 +372,7 @@ Breaking Changes for 3.0.0:
321
372
  (Sam Phippen)
322
373
  * Remove the deprecated `have`, `have_at_least` and `have_at_most` matchers.
323
374
  You can continue using those matchers through https://github.com/rspec/rspec-collection_matchers,
324
- or you can rewrite your expectations with something like
375
+ or you can rewrite your expectations with something like
325
376
  `expect(your_object.size).to eq(num)`. (Hugo Baraúna)
326
377
  * Rename `be_true` and `be_false` to `be_truthy` and `be_falsey`. (Sam Phippen)
327
378
  * Make `expect { }.to_not raise_error(SomeSpecificClass, message)`,
@@ -442,9 +493,9 @@ Deprecations:
442
493
  Deprecations
443
494
 
444
495
  * Deprecate `have`, `have_at_least` and `have_at_most`. You can continue using those
445
- matchers through https://github.com/rspec/rspec-collection_matchers, or
446
- you can rewrite your expectations with something like
447
- `expect(your_object.size).to eq(num)`. (Hugo Baraúna)
496
+ matchers through https://github.com/rspec/rspec-collection_matchers, or
497
+ you can rewrite your expectations with something like
498
+ `expect(your_object.size).to eq(num)`. (Hugo Baraúna)
448
499
  * Deprecate `be_xyz` predicate matcher when `xyz?` is a private method.
449
500
  (Jon Rowe)
450
501
  * Deprecate `be_true`/`be_false` in favour of `be_truthy`/`be_falsey`
data/README.md CHANGED
@@ -95,7 +95,7 @@ Note: The new `expect` syntax no longer supports the `=~` matcher.
95
95
 
96
96
  ```ruby
97
97
  expect(actual).to be_an_instance_of(expected) # passes if actual.class == expected
98
- expect(actual).to be_a(expected) # passes if actual.is_a?(expected)
98
+ expect(actual).to be_a(expected) # passes if actual.kind_of?(expected)
99
99
  expect(actual).to be_an(expected) # an alias for be_a
100
100
  expect(actual).to be_a_kind_of(expected) # another alias
101
101
  ```
@@ -1,6 +1,7 @@
1
1
  require 'rspec/support'
2
2
  RSpec::Support.require_rspec_support "caller_filter"
3
3
  RSpec::Support.require_rspec_support "warnings"
4
+ RSpec::Support.require_rspec_support "object_formatter"
4
5
 
5
6
  require 'rspec/matchers'
6
7
 
@@ -63,7 +64,18 @@ module RSpec
63
64
  # the user sets an expectation, it can't be caught in their
64
65
  # code by a bare `rescue`.
65
66
  # @api public
66
- class ExpectationNotMetError < ::Exception
67
+ class ExpectationNotMetError < Exception
67
68
  end
69
+
70
+ # Exception raised from `aggregate_failures` when multiple expectations fail.
71
+ #
72
+ # @note The constant is defined here but the extensive logic of this class
73
+ # is lazily defined when `FailureAggregator` is autoloaded, since we do
74
+ # not need to waste time defining that functionality unless
75
+ # `aggregate_failures` is used.
76
+ class MultipleExpectationsNotMetError < ExpectationNotMetError
77
+ end
78
+
79
+ autoload :FailureAggregator, "rspec/expectations/failure_aggregator"
68
80
  end
69
81
  end
@@ -18,6 +18,10 @@ module RSpec
18
18
  #
19
19
  # RSpec::Expectations.configuration
20
20
  class Configuration
21
+ def initialize
22
+ @warn_about_potential_false_positives = true
23
+ end
24
+
21
25
  # Configures the supported syntax.
22
26
  # @param [Array<Symbol>, Symbol] values the syntaxes to enable
23
27
  # @example
@@ -133,6 +137,19 @@ module RSpec
133
137
  backtrace
134
138
  end
135
139
  end
140
+
141
+ # Configures whether RSpec will warn about matcher use which will
142
+ # potentially cause false positives in tests.
143
+ #
144
+ # @param value [Boolean]
145
+ attr_writer :warn_about_potential_false_positives
146
+
147
+ # Indicates whether RSpec will warn about matcher use which will
148
+ # potentially cause false positives in tests, generally you want to
149
+ # avoid such scenarios so this defaults to `true`.
150
+ def warn_about_potential_false_positives?
151
+ @warn_about_potential_false_positives
152
+ end
136
153
  end
137
154
 
138
155
  # The configuration object.
@@ -98,9 +98,9 @@ module RSpec
98
98
  def enforce_block_expectation(matcher)
99
99
  return if supports_block_expectations?(matcher)
100
100
 
101
- raise ExpectationNotMetError, "You must pass an argument rather than " \
102
- "a block to use the provided matcher (#{description_of matcher}), or " \
103
- "the matcher must implement `supports_block_expectations?`."
101
+ raise ExpectationNotMetError, "You must pass an argument rather than a block to use the provided " \
102
+ "matcher (#{RSpec::Support::ObjectFormatter.format(matcher)}), or the matcher must implement " \
103
+ "`supports_block_expectations?`."
104
104
  end
105
105
 
106
106
  def supports_block_expectations?(matcher)
@@ -108,12 +108,6 @@ module RSpec
108
108
  rescue NoMethodError
109
109
  false
110
110
  end
111
-
112
- def description_of(matcher)
113
- matcher.description
114
- rescue NoMethodError
115
- matcher.inspect
116
- end
117
111
  end
118
112
  end
119
113
  end
@@ -1,5 +1,3 @@
1
- RSpec::Support.require_rspec_support 'differ'
2
-
3
1
  module RSpec
4
2
  module Expectations
5
3
  class << self
@@ -26,7 +24,7 @@ module RSpec
26
24
 
27
25
  message = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(expected).message_with_diff(message, differ, actual)
28
26
 
29
- raise RSpec::Expectations::ExpectationNotMetError, message
27
+ RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message)
30
28
  end
31
29
  end
32
30
  end
@@ -0,0 +1,194 @@
1
+ module RSpec
2
+ module Expectations
3
+ # @private
4
+ class FailureAggregator
5
+ attr_reader :block_label, :metadata
6
+
7
+ def aggregate
8
+ RSpec::Support.with_failure_notifier(self) do
9
+ begin
10
+ yield
11
+ rescue ExpectationNotMetError => e
12
+ # Normally, expectation failures will be notified via the `call` method, below,
13
+ # but since the failure notifier uses a thread local variable, failing expectations
14
+ # in another thread will still raise. We handle that here and categorize it as part
15
+ # of `failures` rather than letting it fall through and be categorized as part of
16
+ # `other_errors`.
17
+ failures << e
18
+ rescue Exception => e
19
+ # While it is normally a bad practice to rescue `Exception`, it's important we do
20
+ # so here. It's low risk (`notify_aggregated_failures` below will re-raise the exception,
21
+ # or raise a `MultipleExpectationsNotMetError` that includes the exception), and it's
22
+ # essential that the user is notified of expectation failures that may have already
23
+ # occurred in the `aggregate_failures` block. Those expectation failures may provide
24
+ # important diagnostics for understanding why this exception occurred, and if we simply
25
+ # allowed this exception to be raised as-is, it would (wrongly) suggest to the user
26
+ # that the expectation passed when it did not, which would be quite confusing.
27
+ other_errors << e
28
+ end
29
+ end
30
+
31
+ notify_aggregated_failures
32
+ end
33
+
34
+ def failures
35
+ @failures ||= []
36
+ end
37
+
38
+ def other_errors
39
+ @other_errors ||= []
40
+ end
41
+
42
+ # This method is defined to satisfy the callable interface
43
+ # expected by `RSpec::Support.with_failure_notifier`.
44
+ def call(failure, options)
45
+ source_id = options[:source_id]
46
+ return if source_id && @seen_source_ids.key?(source_id)
47
+
48
+ @seen_source_ids[source_id] = true
49
+ assign_backtrace(failure) unless failure.backtrace
50
+ failures << failure
51
+ end
52
+
53
+ private
54
+
55
+ if RSpec::Support::Ruby.jruby?
56
+ # On JRuby, `caller` and `raise` produce different backtraces with regards to `.java`
57
+ # stack frames. It's important that we use `raise` for JRuby to produce a backtrace
58
+ # that has a continuous common section with the raised `MultipleExpectationsNotMetError`,
59
+ # so that rspec-core's truncation logic can work properly on it to list the backtrace
60
+ # relative to the `aggregate_failures` block.
61
+ def assign_backtrace(failure)
62
+ raise failure
63
+ rescue failure.class => e
64
+ failure.set_backtrace(e.backtrace)
65
+ end
66
+ else
67
+ # Using `caller` performs better (and is simpler) than `raise` on most Rubies.
68
+ def assign_backtrace(failure)
69
+ failure.set_backtrace(caller)
70
+ end
71
+ end
72
+
73
+ def initialize(block_label, metadata)
74
+ @block_label = block_label
75
+ @metadata = metadata
76
+ @seen_source_ids = {} # don't want to load stdlib set
77
+ end
78
+
79
+ def notify_aggregated_failures
80
+ all_errors = failures + other_errors
81
+
82
+ case all_errors.size
83
+ when 0 then return nil
84
+ when 1 then RSpec::Support.notify_failure all_errors.first
85
+ else RSpec::Support.notify_failure MultipleExpectationsNotMetError.new(self)
86
+ end
87
+ end
88
+ end
89
+
90
+ # Exception raised from `aggregate_failures` when multiple expectations fail.
91
+ class MultipleExpectationsNotMetError
92
+ # @return [String] The fully formatted exception message.
93
+ def message
94
+ @message ||= (["#{summary}:"] + enumerated_failures + enumerated_errors).join("\n\n")
95
+ end
96
+
97
+ # @return [Array<RSpec::Expectations::ExpectationNotMetError>] The list of expectation failures.
98
+ def failures
99
+ @failure_aggregator.failures
100
+ end
101
+
102
+ # @return [Array<Exception>] The list of other exceptions.
103
+ def other_errors
104
+ @failure_aggregator.other_errors
105
+ end
106
+
107
+ # @return [Array<Exception>] The list of expectation failures and other exceptions, combined.
108
+ attr_reader :all_exceptions
109
+
110
+ # @return [String] The user-assigned label for the aggregation block.
111
+ def aggregation_block_label
112
+ @failure_aggregator.block_label
113
+ end
114
+
115
+ # @return [Hash] The metadata hash passed to `aggregate_failures`.
116
+ def aggregation_metadata
117
+ @failure_aggregator.metadata
118
+ end
119
+
120
+ # @return [String] A summary of the failure, including the block label and a count of failures.
121
+ def summary
122
+ "Got #{exception_count_description} from failure aggregation " \
123
+ "block#{block_description}"
124
+ end
125
+
126
+ # return [String] A description of the failure/error counts.
127
+ def exception_count_description
128
+ failure_count = pluralize("failure", failures.size)
129
+ return failure_count if other_errors.empty?
130
+ error_count = pluralize("other error", other_errors.size)
131
+ "#{failure_count} and #{error_count}"
132
+ end
133
+
134
+ private
135
+
136
+ def initialize(failure_aggregator)
137
+ @failure_aggregator = failure_aggregator
138
+ @all_exceptions = failures + other_errors
139
+ end
140
+
141
+ def block_description
142
+ return "" unless aggregation_block_label
143
+ " #{aggregation_block_label.inspect}"
144
+ end
145
+
146
+ def pluralize(noun, count)
147
+ "#{count} #{noun}#{'s' unless count == 1}"
148
+ end
149
+
150
+ def enumerated(exceptions, index_offset)
151
+ exceptions.each_with_index.map do |exception, index|
152
+ index += index_offset
153
+ formatted_message = yield exception
154
+ "#{index_label index}#{indented formatted_message, index}"
155
+ end
156
+ end
157
+
158
+ def enumerated_failures
159
+ enumerated(failures, 0, &:message)
160
+ end
161
+
162
+ def enumerated_errors
163
+ enumerated(other_errors, failures.size) do |error|
164
+ "#{error.class}: #{error.message}"
165
+ end
166
+ end
167
+
168
+ def indented(failure_message, index)
169
+ line_1, *rest = failure_message.strip.lines.to_a
170
+ first_line_indentation = ' ' * (longest_index_label_width - width_of_label(index))
171
+
172
+ first_line_indentation + line_1 + rest.map do |line|
173
+ line =~ /\S/ ? indentation + line : line
174
+ end.join
175
+ end
176
+
177
+ def indentation
178
+ @indentation ||= ' ' * longest_index_label_width
179
+ end
180
+
181
+ def longest_index_label_width
182
+ @longest_index_label_width ||= width_of_label(failures.size)
183
+ end
184
+
185
+ def width_of_label(index)
186
+ index_label(index).chars.count
187
+ end
188
+
189
+ def index_label(index)
190
+ " #{index + 1}) "
191
+ end
192
+ end
193
+ end
194
+ end
@@ -7,6 +7,19 @@ Minitest::Test.class_eval do
7
7
  assert(true) # so each expectation gets counted in minitest's assertion stats
8
8
  super
9
9
  end
10
+
11
+ # Convert a `MultipleExpectationsNotMetError` to a `Minitest::Assertion` error so
12
+ # it gets counted in minitest's summary stats as a failure rather than an error.
13
+ # It would be nice to make `MultipleExpectationsNotMetError` subclass
14
+ # `Minitest::Assertion`, but Minitest's implementation does not treat subclasses
15
+ # the same, so this is the best we can do.
16
+ def aggregate_failures(*args, &block)
17
+ super
18
+ rescue RSpec::Expectations::MultipleExpectationsNotMetError => e
19
+ assertion_failed = Minitest::Assertion.new(e.message)
20
+ assertion_failed.set_backtrace e.backtrace
21
+ raise assertion_failed
22
+ end
10
23
  end
11
24
 
12
25
  module RSpec
@@ -2,7 +2,7 @@ module RSpec
2
2
  module Expectations
3
3
  # @private
4
4
  module Version
5
- STRING = '3.2.1'
5
+ STRING = '3.3.0'
6
6
  end
7
7
  end
8
8
  end
@@ -3,7 +3,7 @@ RSpec::Support.require_rspec_support 'matcher_definition'
3
3
  RSpec::Support.define_optimized_require_for_rspec(:matchers) { |f| require_relative(f) }
4
4
 
5
5
  %w[
6
- pretty
6
+ english_phrasing
7
7
  composable
8
8
  built_in
9
9
  generated_descriptions
@@ -243,7 +243,7 @@ module RSpec
243
243
  # alias $1 $2
244
244
  def self.alias_matcher(new_name, old_name, options={}, &description_override)
245
245
  description_override ||= lambda do |old_desc|
246
- old_desc.gsub(Pretty.split_words(old_name), Pretty.split_words(new_name))
246
+ old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
247
247
  end
248
248
  klass = options.fetch(:klass) { AliasedMatcher }
249
249
 
@@ -277,6 +277,42 @@ module RSpec
277
277
  alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
278
278
  end
279
279
 
280
+ # Allows multiple expectations in the provided block to fail, and then
281
+ # aggregates them into a single exception, rather than aborting on the
282
+ # first expectation failure like normal. This allows you to see all
283
+ # failures from an entire set of expectations without splitting each
284
+ # off into its own example (which may slow things down if the example
285
+ # setup is expensive).
286
+ #
287
+ # @param label [String] label for this aggregation block, which will be
288
+ # included in the aggregated exception message.
289
+ # @param metadata [Hash] additional metadata about this failure aggregation
290
+ # block. If multiple expectations fail, it will be exposed from the
291
+ # {Expectations::MultipleExpectationsNotMetError} exception. Mostly
292
+ # intended for internal RSpec use but you can use it as well.
293
+ # @yield Block containing as many expectation as you want. The block is
294
+ # simply yielded to, so you can trust that anything that works outside
295
+ # the block should work within it.
296
+ # @raise [Expectations::MultipleExpectationsNotMetError] raised when
297
+ # multiple expectations fail.
298
+ # @raise [Expectations::ExpectationNotMetError] raised when a single
299
+ # expectation fails.
300
+ # @raise [Exception] other sorts of exceptions will be raised as normal.
301
+ #
302
+ # @example
303
+ # aggregate_failures("verifying response") do
304
+ # expect(response.status).to eq(200)
305
+ # expect(response.headers).to include("Content-Type" => "text/plain")
306
+ # expect(response.body).to include("Success")
307
+ # end
308
+ #
309
+ # @note The implementation of this feature uses a thread-local variable,
310
+ # which means that if you have an expectation failure in another thread,
311
+ # it'll abort like normal.
312
+ def aggregate_failures(label=nil, metadata={}, &block)
313
+ Expectations::FailureAggregator.new(label, metadata).aggregate(&block)
314
+ end
315
+
280
316
  # Passes if actual is truthy (anything but false or nil)
281
317
  def be_truthy
282
318
  BuiltIn::BeTruthy.new
@@ -732,7 +768,7 @@ module RSpec
732
768
  # expect { do_something_risky }.to raise_error(PoorRiskDecisionError, /oo ri/)
733
769
  #
734
770
  # expect { do_something_risky }.not_to raise_error
735
- def raise_error(error=Exception, message=nil, &block)
771
+ def raise_error(error=nil, message=nil, &block)
736
772
  BuiltIn::RaiseError.new(error, message, &block)
737
773
  end
738
774
  alias_method :raise_exception, :raise_error
@@ -767,10 +803,13 @@ module RSpec
767
803
  # If you do find yourself in such a situation, you could always write
768
804
  # a custom matcher, which would likely make your specs more expressive.
769
805
  #
806
+ # @param description [String] optional description to be used for this matcher.
807
+ #
770
808
  # @example
771
809
  # expect(5).to satisfy { |n| n > 3 }
772
- def satisfy(&block)
773
- BuiltIn::Satisfy.new(&block)
810
+ # expect(5).to satisfy("be greater than 3") { |n| n > 3 }
811
+ def satisfy(description="satisfy block", &block)
812
+ BuiltIn::Satisfy.new(description, &block)
774
813
  end
775
814
  alias_matcher :an_object_satisfying, :satisfy
776
815
  alias_matcher :satisfying, :satisfy
@@ -915,6 +954,7 @@ module RSpec
915
954
 
916
955
  BE_PREDICATE_REGEX = /^(be_(?:an?_)?)(.*)/
917
956
  HAS_REGEX = /^(?:have_)(.*)/
957
+ DYNAMIC_MATCHER_REGEX = Regexp.union(BE_PREDICATE_REGEX, HAS_REGEX)
918
958
 
919
959
  def method_missing(method, *args, &block)
920
960
  case method.to_s
@@ -927,6 +967,20 @@ module RSpec
927
967
  end
928
968
  end
929
969
 
970
+ if RUBY_VERSION.to_f >= 1.9
971
+ def respond_to_missing?(method, *)
972
+ method =~ DYNAMIC_MATCHER_REGEX || super
973
+ end
974
+ else # for 1.8.7
975
+ # :nocov:
976
+ def respond_to?(method, *)
977
+ method = method.to_s
978
+ method =~ DYNAMIC_MATCHER_REGEX || super
979
+ end
980
+ public :respond_to?
981
+ # :nocov:
982
+ end
983
+
930
984
  # @api private
931
985
  def self.is_a_matcher?(obj)
932
986
  return true if ::RSpec::Matchers::BuiltIn::BaseMatcher === obj