Predicate matchers

Ruby objects commonly provide predicate methods:

    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:

    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:

    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.

    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.

Expecting subject to be_zero (based on Integer#zero?)

Given a file named “shouldbezero_spec.rb” with:

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”.

Expecting subject to not be_empty (based on Array#empty?)

Given a file named “shouldnotbeemptyspec.rb” with:

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”.

Expecting subject to have_key (based on Hash#has_key?)

Given a file named “shouldhavekey_spec.rb” with:

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”.

Expecting subject to have all decimals (based on custom has_decimals? method)

Given a file named “shouldnothaveallstringkeysspec.rb” with:

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”.

Matcher arguments are passed on to the predicate method

Given a file named “predicatematcherargument_spec.rb” with:

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”.

The config strict_predicate_matchers impacts matching of results other than true and false

Given a file named “strictornot.rb” with:

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 “hasstrangeresult?` to return true, got 42”.

Calling private method with be_predicate causes error

Given a file named “attemptingtomatchprivatemethod_spec.rb” with:

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”.

Calling private method with have_predicate causes error

Given a file named “attemptingtomatchprivatemethod_spec.rb” with:

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”.