RSpec Predicate Matchers

Für Methoden, die True oder False zurückgeben, gibt es in Ruby die Konvention, den Methodennamen mit einem ? abzuschliessen. RSpec bietet für diese Konvention eine verkürzte Testnotation an, sogenannte predicate matchers. Damit kann die Testerwartung sehr viel prägnanter ausgedrückt werden.

Lange RSpec Tests

Zunächst eine beispielhafte Ausgangssituation: eine Stadt ist eine Metropole, wenn sie mehr als eine Million Einwohner hat. Die Implementierung soll in der Methode City#metropolis? stattfinden.
Die Erwartungshaltung 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

Allerdings können die selben Erwartungen auch sehr viel kürzer und prägnanter ausgedrückt werden.

Kurze RSpec Tests

Die selben 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

Genau wie die RSpec eigenen Matcher be_empty oder be_nil erwartet RSpec bei den predicate matchers, dass das Testobjekt eine Methode hat, die sich aus dem Matcher ableiten läßt: be_xxx => xxx? und ruft diesen auf.

Implementierung

Die Implementierung des Beispiels:

class City
  METROPOLIS_POPULATION = 1_000_000

  attr_accessor :population

  def metropolis?
    population.to_i >= METROPOLIS_POPULATION
  end
end

und die Testergebnisse:

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