1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
Feature: Predicate matchers
Ruby objects commonly provide predicate methods:
```ruby
7.zero? # => false
0.zero? # => true
[1].empty? # => false
[].empty? # => true
{ :a => 5 }.has_key?(:b) # => false
{ :b => 5 }.has_key?(:b) # => true
```
You could use a basic equality matcher to set expectations on these:
```ruby
expect(7.zero?).to eq true # fails with "expected true, got false (using ==)"
```
...but RSpec provides dynamic predicate matchers that are more readable and provide
better failure output.
For any predicate method, RSpec gives you a corresponding matcher. Simply prefix the
method with `be_` and remove the question mark. Examples:
```ruby
expect(7).not_to be_zero # calls 7.zero?
expect([]).to be_empty # calls [].empty?
expect(x).to be_multiple_of(3) # calls x.multiple_of?(3)
```
Alternately, for a predicate method that begins with `has_` like `Hash#has_key?`, RSpec allows
you to use an alternate form since `be_has_key` makes no sense.
```ruby
expect(hash).to have_key(:foo) # calls hash.has_key?(:foo)
expect(array).not_to have_odd_values # calls array.has_odd_values?
```
In either case, RSpec provides nice, clear error messages, such as:
`expected zero? to be truthy, got false`
Calling private methods will also fail:
`expected private_method? to return true but it's a private method`
Any arguments passed to the matcher will be passed on to the predicate method.
Scenario: Expecting `subject` to `be_zero` (based on Integer#zero?)
Given a file named "should_be_zero_spec.rb" with:
"""ruby
RSpec.describe 0 do
it { is_expected.to be_zero }
end
RSpec.describe 7 do
it { is_expected.to be_zero } # deliberate failure
end
"""
When I run `rspec should_be_zero_spec.rb`
Then the output should contain "2 examples, 1 failure"
And the output should contain "expected `7.zero?` to be truthy, got false"
Scenario: Expecting `subject` to not `be_empty` (based on Array#empty?)
Given a file named "should_not_be_empty_spec.rb" with:
"""ruby
RSpec.describe [1, 2, 3] do
it { is_expected.not_to be_empty }
end
RSpec.describe [] do
it { is_expected.not_to be_empty } # deliberate failure
end
"""
When I run `rspec should_not_be_empty_spec.rb`
Then the output should contain "2 examples, 1 failure"
And the output should contain "expected `[].empty?` to be falsey, got true"
Scenario: Expecting `subject` to `have_key` (based on Hash#has_key?)
Given a file named "should_have_key_spec.rb" with:
"""ruby
RSpec.describe Hash do
subject { { :foo => 7 } }
it { is_expected.to have_key(:foo) }
it { is_expected.to have_key(:bar) } # deliberate failure
end
"""
When I run `rspec should_have_key_spec.rb`
Then the output should contain "2 examples, 1 failure"
And the output should contain "expected `{:foo=>7}.has_key?(:bar)` to be truthy, got false"
Scenario: Expecting `subject` to have all decimals (based on custom `has_decimals?` method)
Given a file named "should_not_have_all_string_keys_spec.rb" with:
"""ruby
class Float
def has_decimals?
round != self
end
end
RSpec.describe Float do
context 'with decimals' do
subject { 4.2 }
it { is_expected.to have_decimals }
end
context 'with no decimals' do
subject { 42.0 }
it { is_expected.to have_decimals } # deliberate failure
end
end
"""
When I run `rspec should_not_have_all_string_keys_spec.rb`
Then the output should contain "2 examples, 1 failure"
And the output should contain "expected `42.0.has_decimals?` to be truthy, got false"
Scenario: Matcher arguments are passed on to the predicate method
Given a file named "predicate_matcher_argument_spec.rb" with:
"""ruby
class Integer
def multiple_of?(x)
(self % x).zero?
end
end
RSpec.describe 12 do
it { is_expected.to be_multiple_of(3) }
it { is_expected.not_to be_multiple_of(7) }
# deliberate failures
it { is_expected.not_to be_multiple_of(4) }
it { is_expected.to be_multiple_of(5) }
end
"""
When I run `rspec predicate_matcher_argument_spec.rb`
Then the output should contain "4 examples, 2 failures"
And the output should contain "expected `12.multiple_of?(4)` to be falsey, got true"
And the output should contain "expected `12.multiple_of?(5)` to be truthy, got false"
Scenario: The config `strict_predicate_matchers` impacts matching of results other than `true` and `false`
Given a file named "strict_or_not.rb" with:
"""ruby
class StrangeResult
def has_strange_result?
42
end
end
RSpec.describe StrangeResult do
subject { StrangeResult.new }
before do
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.strict_predicate_matchers = strict
end
end
end
context 'with non-strict matchers (default)' do
let(:strict) { false }
it { is_expected.to have_strange_result }
end
context 'with strict matchers' do
let(:strict) { true }
# deliberate failure
it { is_expected.to have_strange_result }
end
end
"""
When I run `rspec strict_or_not.rb`
Then the output should contain "2 examples, 1 failure"
And the output should contain "has_strange_result?` to return true, got 42"
Scenario: Calling private method with be_predicate causes error
Given a file named "attempting_to_match_private_method_spec.rb" with:
"""ruby
class WithPrivateMethods
def secret?
true
end
private :secret?
end
RSpec.describe 'private methods' do
subject { WithPrivateMethods.new }
# deliberate failure
it { is_expected.to be_secret }
end
"""
When I run `rspec attempting_to_match_private_method_spec.rb`
Then the output should contain "1 example, 1 failure"
And the output should contain "`secret?` is a private method"
Scenario: Calling private method with have_predicate causes error
Given a file named "attempting_to_match_private_method_spec.rb" with:
"""ruby
class WithPrivateMethods
def has_secret?
true
end
private :has_secret?
end
RSpec.describe 'private methods' do
subject { WithPrivateMethods.new }
# deliberate failure
it { is_expected.to have_secret }
end
"""
When I run `rspec attempting_to_match_private_method_spec.rb`
Then the output should contain "1 example, 1 failure"
And the output should contain "`has_secret?` is a private method"
|