Skip to content

Tutorial: invariants

jimweirich edited this page Apr 17, 2013 · 3 revisions

This is part of the RSpec/Given Tutorial.

Invariants

Invariants are specification assertions that are evaluated just before each Then in scope. This allows us to specify that something is true across a range of tests without repeating the assertion at every point.

A simple example of might be the definition of the "dead?" method on a character. A character will be considered dead if their energy level drops below zero. We capture that definition at the top level of the spec, assuring that the truth of that assertion is verify at every Then clause in scope.

describe "Character can be damaged" do
  LOSING_DICE_ROLL = 2
  WINNING_DICE_ROLL = 19
  CRITICAL_DICE_ROLL = 20

  Given(:attacker) { Character.new("Attacker") }
  Given(:defender) { Character.new("Defender") }
  Given!(:original_hp) { defender.hit_points }

  Invariant { defender.should be_alive }

  When { attacker.attack(defender, dice_value) }
  Given(:change_in_hp) { defenders.hit_points - original_hp }

  context "when the attacker misses" do
    Given(:dice_value) { LOSING_DICE_ROLL }
    Then { change_in_hp.should == 0 }
  end

  context "when the attacker hits" do
    Given(:dice_value) { WINNING_DICE_ROLL }
    Then { change_in_hp.should == -1 }
  end

  context "when the attacker gets a critical hit" do
    Given(:dice_value) { CRITICAL_DICE_ROLL }
    Then { change_in_hp.should == -2 }
  end
end

Scoped Invariants

Sometimes we just wish to assert some is true across a subset of all specifications. We can do that by putting the Invariant clause in a nested describe/context block.

Imagine that we break up our attack and damage specification into two sections, one where the defender survives and one where the defender dies.

describe "Character can be damaged" do
  LOSING_DICE_ROLL = 2
  WINNING_DICE_ROLL = 19
  CRITICAL_DICE_ROLL = 20

  Given(:attacker) { Character.new("Attacker") }
  Given(:defender) { Character.new("Defender") }
  Given!(:original_hp) { defender.hit_points }

  When { attacker.attack(defender, dice_value) }
  Given(:change_in_hp) { defenders.hit_points - original_hp }

  context "where the defender survives" do
    Invariant { defender.should be_alive }

    context "when the attacker misses" do
      Given(:dice_value) { LOSING_DICE_ROLL }
      Then { change_in_hp.should == 0 }
    end

    context "when the attacker hits" do
      Given(:dice_value) { WINNING_DICE_ROLL }
      Then { change_in_hp.should == -1 }
    end

    context "when the attacker gets a critical hit" do
      Given(:dice_value) { CRITICAL_DICE_ROLL }
      Then { change_in_hp.should == -2 }
    end
  end

  context "where the defender dies" do
    Given { defender.takes_damage(defender.hit_points - 1) }
    When { attacker.attack(defender, 19) }
    Then { defender.should be_dead }
  end
end

-- Previous: Tutorial: Inherited when