RSpec predicate matchers

For methods that return True or False, there is a convention in Ruby to end the method name with a ?. RSpec provides a shortened test notation, so-called predicate matchers for that convention. Thus the test expectation can be expressed way more concisely.

Long RSpec Tests

First, an exemplary starting point: a city is a metropolis when it has more than a million citizen. The implementation is meant to take place in the method City#metropolis?.
The expectations in RSpec:

describe City do
  subject { City.new }

  describe '#metropolis?' do
    context 'without population' do
      it 'is false' do
        expect(subject.metropolis?).to be false
      end
    end

    context 'when population is less than a million' do
      before { city.population = 999_999 }
      it 'is false' do
        expect(subject.metropolis?).to be false
      end
    end

    context 'when population is equal to a million' do
      before { city.population = 1_000_000 }
      it 'is true' do
        expect(subject.metropolis?).to be true
      end
    end
  end
end

However, the same expectations can be expressed way more concisely.

Concise RSpec Tests

The same tests:

describe City do
  subject { City.new }

  describe '#metropolis?' do
    context 'without population' do
      it { is_expected.to_not be_metropolis }
    end

    context 'when population is less than a million' do
      before { subject.population = 999_999 }
      it { is_expected.to_not be_metropolis }
    end

    context 'when population is equal to a million' do
      before { city.population = 1_000_000 }
      it { is_expected.to be_metropolis }
    end
  end
end

Like the RSpec’s built in matchers be_empty or be_nil, RSpec expects the test object to have a method that can be derived from the matcher: be_xxx => xxx?, which then is called.

Implementation

The implementation of the example:

class City
  METROPOLIS_POPULATION = 1_000_000

  attr_accessor :population

  def metropolis?
    population.to_i >= METROPOLIS_POPULATION
  end
end

and the test results:

City
  #metropolis?
    without population
      should not be metropolis
    when population less than a million
      should not be metropolis
    when population equal to a million
      should be metropolis

Finished in 0.00358 seconds (files took 0.48847 seconds to load)
3 examples, 0 failures