Writing Rails specs with RSpec and FactoryGirl is easy to do when you got a basic understanding of testing principles but you may have noticed how these specs tends to get cluttered over time. Even to the point you don’t get what’s going on at all and call your co-worker who wrote them and ask him to handle your task!

The following points are basic principles to keep in mind while writing specs to avoid being stuck with an unreadable spec.

{% end_excerpt %}

We deal with four models : User, Cart, Order and Item. Their relationships are obviously as simplified as possible to keep ourselves focused on their tests.

class User
  has_one :cart
  has_many :orders
end

class Cart
  belongs_to :user
  has_many :items
end

class Order
  has_many :items
  belongs_to :user
end

class Item
  belongs_to :cart
  belongs_to :order
  belongs_to :product
end

Don’t Repeat Yourself

As usual, the DRY principle. Consider the following code (user_spec.rb) :

describe User do
  before :each do
    @user = Factory.create :user
  end

  it "should order the cart with one item" do
    @cart = Factory.create :cart, :user => @user
    @item = Factory.create :item, :cart => @cart

    @user.order! @cart
    @cart.should be_ordered
  end

  it "should discard the cart" do
    @cart = Factory.create :cart, :user => @user
    @item = Factory.create :item, :cart => @cart

    @user.discard_cart
    @cart.items.should be_empty
  end
end

Quite clean by itself, we create a user for each test, as expected for a spec about the user model. Yet you can easily notice we’re building other models in our two tests.

We can factorize these factories instanciation to stay DRY :

describe User do
  before :each do
    @user    = Factory.create :user
    @cart    = Factory.create :cart, :user => @user
    @item    = Factory.create :item, :cart => @cart
  end

  it "should order the cart with one item" do
    @user.order! @cart
    @cart.should be_ordered
  end

  it "should discard the cart" do
    @user.discard_cart
    @cart.items.should be_empty
  end
end

Now we got two tests and this example rise the following principle :

Test code should be almost a direct translation of its name

Any context initialization, should be done in a before block to avoid polluting the test code itself.

Enhance readability

As we avoid to pollute code to enhance readability, we can also emphasize on what’s important. It allows the reader to grasp with ease what’s going on.

describe User do
  before :each do
    @user    = Factory.create :user
    @cart    = Factory.create :cart, :user => @user
    @item    = Factory.create :item

    @cart.items << @item # focus on adding an item
  end

  # ...
end

The main point of this before block is to craft a cart with an item within. As factories are cool, it doesn’t mean we have to use their features all the time.

Using the << operator on line 7, on the items association emphasize on adding our item to the cart instead of diluting it through the factories. This line of is the most important considering we’re testing how a user interacts with items.

So while writing your test code, be sure to avoid embedding your intentions in the basic plumbing.

One expectation per test please

To pursue in our readability quest, you may have noticed that the example used in the previous points was really simple. But what makes theses so simple ? Those two tests got only one expectation at a time.

Consider the following code :

describe User do
  before :each do
    @user = Factory.create :user
    @cart = Factory.create :cart, :user => @user
    @item = Factory.create :item, :cart => @cart

    @order = @user.order! @cart
  end

  it "should finalize the order" do
    @order.finalize!
    @user.should have(1).finalized_orders
    @order.should be_finalized
  end
end

We’ve got two should call there. Even if it’s just slightly more complicated than before, you can separate concerns. We wrote describe User meaning we talk about user here. We do not want to mix expectations about orders and users.

Accordingly expectation on line 12 , even if being really similar to line 11 has nothing to do here. So we can rewrite this test in two separated tests (order_spec.rb) :

describe Order do
  before :each do
    @cart  = Factory.create :cart
    @user  = @cart.user
    @cart.items << Factory.create_list :item, 3

    @order = @user.order! @cart
  end

  it "should finalize the order" do
    @order.finalize
    @order.should be_finalized
  end
end



describe User do
  before :each do
    @user = Factory.create :user
    @cart = Factory.create :cart, :user => @user
    @item = Factory.create :item, :cart => @cart

    @order = @user.order! @cart
  end

  it "should finalize the order" do
    @order.finalize!
    @user.should have(1).finalized_orders
  end
end

Plain simple, just formulate expectations about your current subject while writing test and ignore the rest. Why ? Because if you don’t you’re leaving the coast of unit tests to head around integration testing land.

Slice your specs with different contexts

When it comes to models, there’s a lot to handle. Business logic, mass-assignements, validation sanity.( Remember fat models for skinny controllers eh ? It’s for a reason ! )

While you can argue if you should test validations and assignments or not, which is out of the topic here, we still have to test for a wide range of business logic cases.

All of these case can easily be sliced by concerns, for example a user can be edited and can order items through a cart. An easy way to name contexts is to use the ing form of the verb describing the action :

describe User do
  context "editing informations" do
    # ...
  end

  context "ordering items" do
    # ...
  end

  context "canceling cart" do
    # ...
  end
end

Writing “while editing …” or “when editing …” is a matter of taste, while I personally tend to prefer a concise description.

And if we add validations and assignments ? (helpers are provided by should-matchers)

describe User do
  describe "validations" do
    it { should validate_presence_of(:email) }
    it { should validate_presence_of(:name) }
  end

  describe "assignments" do
    it { should allow_mass_assignment_of(:email) }
    it { should allow_mass_assignment_of(:name) }

    it { should_not allow_mass_assignment_of(:administrator) }
  end

  context "editing informations" do
    # ...
  end

  context "ordering items" do
    # ...
  end

  context "canceling cart" do
    # ...
  end
end

That makes a readable skeleton for our tests.

The point of writing specs is to keep them enjoyable and litteraly act as documentation for everyone. Those four advices are just basics but at least ensure you’re heading in the right direction with your specs.