Bogdan Gusiev's blog

How to make good software for people


Dynamic context in Rspec - don't repeat yourself
03 Nov 2010

We used to have less problems with subject and behavior when writing our unit tests. And context is what testing is all about. Web apps manage data and the same method can return different values based on current database state. Advanced rspec features make it possible to use very effective technique to organize tests. In order to reflect your non-linear business process in unit tests you can not only nest contexts in each other but also make your context more dynamic.

Context callback

Rspec #let method is used to lazy load things into context. In spite of lazy initialization, let blocks are defined before the before each hook. This allow us to create context callback.

Understand by example. In this example orders should be delivered to confirmed customer account just after creation and should not be delivered if the account is not confirmed yet.
describe Order do
  context "after create" do #defining a partial context
    before(:each) do
      subject.customer.confirmed = _confirmed
      subject.save!
    end

    context "when customer is confirmed" do 
      let(:_confirmed) { true }
      it { should be_delivered }
    end
    
    context "when customer is not confirmed" do 
      let(:_confirmed) { false }
      it { should_not be_delivered }
    end
  end
end
Here you can see partial context definition and custom behavior in two nested contexts. I used underscore prefix in order to identify the callback method, that is defined differently in different contexts.

This happened because let as method is defined at once but before block is called only when it is reached.

Testing utility function

Another example that is a kind of pattern matching(Erlang term), designed to test utility functions.
Suppose we have a boolean expression evaluation function:
describe Expression
  describe ".run" do

    subject { Expression.run(arg) }

    context "with '&' where both true" do
      let(:arg) { "true & true" }
      it {should be_true}
    end

    context "with '&' where one false" do
      let(:arg) { "false & true" }
      it {should be_false}
    end
    ........
  end
end
Very good strategy to run same function with different arguments.

Summary

Rspec is far ahead of all unit testing frameworks. Unlike most of Rspec clones (e.g. for other programming languages), Rspec authors got in deep to the testing problems and invent flexible and elegant syntax.

behavior rspec context test dry