Developing Spree extension with TDD

Alberto Vena

13 Aug 2012 Development, Ruby On Rails

Alberto Vena

7 mins
test driven development

Here at Nebulab, we have just built an extension (Spree Subscriptions) that adds to Spree features needed to buy and manage subscription based items, ideal for magazines or similar products. We developed this extension for one of our clients and it has been our first official extension. Starting from scratch allowed us to better undestand how to develop Spree extensions using a TDD process and we wanted to share some thoughts and best practices about this topic. To make a rapid overview, in this article we will create a new extension, using some spree-subscriptions real scenarios and code, that adds a checkbox to the products edit page to denote if it is a subscribable product (a magazine). We will start writing tests and then we will make them pass.

Before starting

Choose the right tools

A Spree extension is essentially a simple Rails engine that has the spree_core gem (another engine) as dependency; this means we can use any preferred library to test it. Since Spree uses Rspec, FactoryGirl and Capybara we think that the best approach is to use the same tools in order to make Spree developers feel comfortable with tools they are used to work with and, eventually, reproduce or use some code already present in spree core. For example, in our extension we can reuse spree core factories or create new factories that have them as parents.

Create the new extension

It's suggested to read Creating Extension Spree Official Guide before starting, which explores better this topic.

$ gem install rails
$ gem install spree

Now we can create the extension using the spree extension command.

$ cd ~/path/to/your/Code
$ spree extension Subscriptions
$ cd spree_subscriptions

Note: This guide refers to Spree 1.1 version of Spree. I think the only difference with the 1.2 (actually RC) will be with handling spree_auth that will not be a strict dependency of spree-core, letting us choose our favourite authentication system.

Generate a resource

Althoughe we have not to generate a resource for our extension example scenario, it could be useful to see how to do it and how to automatically create rspec files with a simple command. We have to add rspec gem to our extension Gemfile:

group :test do
  gem 'rspec'
end

After a bundle install we can lauch:

$ rails g resource spree/subscription
      invoke  active_record
      create    db/migrate/20120705112710_create_spree_subscriptions.rb
      create    app/models/spree/subscription.rb
      create    app/models/spree.rb
      invoke    rspec
      create      spec/models/spree/subscription_spec.rb
      invoke  controller
      create    app/controllers/spree/subscriptions_controller.rb
      invoke    erb
      create      app/views/spree/subscriptions
      invoke    rspec
      create      spec/controllers/spree/subscriptions_controller_spec.rb
      invoke    helper
      create      app/helpers/spree/subscriptions_helper.rb
      invoke      rspec
      create        spec/helpers/spree/subscriptions_helper_spec.rb
      invoke    assets
      invoke      js
      create        app/assets/javascripts/spree/subscriptions.js
      invoke      css
      create        app/assets/stylesheets/spree/subscriptions.css
      invoke  resource_route
       route    namespace :spree do resources :subscriptions end

And the tree will be correctly generated with rspec files too.

Include local factories

By default Spree extensions require the factories used for spree core testing. In fact into the spec_helper we can find:

require 'spree/core/testing_support/factories'

These factories can be very useful because we are sure we are modeling our test following real spree objects but if we want to test our extension we probably want to add some new factories. To add this feature we can simply add this lines after the core factories require:

Dir["#{File.dirname(__FILE__)}/factories/**/*.rb"].each do |f|
  fp =  File.expand_path(f)
  require fp
end

Now we can add our custom factories into spec/factories directory. We can also extend existing spree core factories using them as parents for our new factories, for example:

FactoryGirl.define do
  factory :subscribable_product, :parent => :product do
    subscribable true
  end
end
Using spree preferences in our tests

Testing an extension will probably let us bump into the setup of some spree preferences. Spree Core is tested resetting preferences to default values each time they are used to ensure tests always run in a default environment. To reproduce this behavior in our extension we can simply create (copy) a support method into /spec/support/preferences.rb

def reset_spree_preferences
  Spree::Preferences::Store.instance.persistence = false
  config = Rails.application.config.spree.preferences
  config.reset
  yield(config) if block_given?
end

that will allow us to setup required preferences in our tests:

before(:each) do
  reset_spree_preferences do |config|
    config.default_country_id = create(:country).id
    config.site_name = "my dummy test store for subscriptions"
  end
end
Add auth gem to test as an admin user

To test as admin interface functionality we have to sign in as admin user. To do it we have to require spree_auth into our lib/spree_subscriptions.rb file. which now will be like this:

require 'spree_core'
require 'spree_auth'
require 'spree_subscriptions/engine'

In order to complete login easily throught integration tests we can define another support method "sign_in_as!", taken directly from spree_auth gem:

def sign_in_as!(user)
  visit '/login'
  fill_in 'Email', :with => user.email
  fill_in 'Password', :with => 'secret'
  click_button 'Login'
end

Run tests on extension

To test our extension we need to crate a dummy test app that will reside into spec/dummy/. This app will contain everything needed to execute the tests from our extension. Before running rspec for the first time and every time we make a structural change to the extension (like changing or adding a migration) we need to create the test app with this command:

$ rake test_app

Once completed we can run tests within our extension with:

$ rspec spec
Add some integration tests

Let's say we want to test the ability of an admin user to flag a product as subscribable through the admin panel. We need to create some integation tests first. In spec/requests/admin/product_spec.rb:

require 'spec_helper'

describe "Product" do
  context "on edit" do
    it "should be selected as subscribable" do
      product = create(:product)
      user = create(:admin_user, :email => "[email protected]")
      sign_in_as!(user)
      visit spree.admin_path
      click_link "Products"
      within('table.index tr:nth-child(2)') { click_link "Edit" }
      check('product_subscribable')
      click_button "Update"
      page.should have_content("successfully updated!")
      page.has_checked_field?('product_subscribable').should == true
    end
  end
end

This test will not pass because the checkbox doesn't exist yet. We can create with this Deface override:

Deface::Override.new(:virtual_path => "spree/admin/products/_form",
                     :name => "adds_subscribable_to_product",
                     :insert_bottom => "[data-hook='admin_product_form_right']",
                     :partial => "spree/admin/products/subscription_field")

As you can see, this override will render a partial in the right part of the product edit form. This partial contains:

<%= f.field_container :subscribable do %>
  <%= f.label :subscribable %><br />
  <%= f.check_box :subscribable %>
<% end %>

Test fails agin. This time because there are no setter and getter method for "subscribable" attribute. It's time to add some model tests.

Add some model tests

Into spec/model/product_spec.rb we can add this code that test the existence of subscribable method and its default value:

require 'spec_helper'

describe Spree::Product do
  let(:subscribable_product) { Factory(:subscribable_product) }
  let(:simple_product) { Factory(:simple_product) }

  it "should respond to subscribable method" do
    subscribable_product.should respond_to :subscribable
  end

  it "should be subscribable" do
    subscribable_product.subscribable.should be_true
  end

  it "should have subscribable to false by default" do
    simple_product.subscribable?.should be false
  end
end

In order to let these tests pass, we have to add the subscribable field to the product table with a migration, then we have to recreate the test app to update its database that will include this last migration.

$ rails g migration add_subscribable_to_spree_products subscribable:boolean
$ rake test_app

We can try to run test but they will continue to fail. This time the issue is with mass assignment of subscribable attribute. To solve this problem we have to extend the product model creating a product decorator file in app/models/spree/product_decorator.rb:

Spree::Product.class_eval do
  attr_accessible :subscribable
end

These simple example tests pass so we can now be sure an admin user can set a product as subscribable.

Get a screenshot on test failure

Sometime we found really useful to view a screenshot (in .html format) of the page a test fails. There is a gem that does exactly the job: 'capybara-screenshot'. To add it to our spree extension we can modify our Gemfile adding the gem to the test group:

group :test do
  gem 'capybara-screenshot', :require => false
end

and requiring it after rspec/rails in the spec_helper

...
require 'rspec/rails'
require 'capybara-screenshot'
require 'ffaker'
...

After a test failure we can find the html screenshot in /spec/dummy/tmp/capybara/ folder. Keep in mind that these files will be deleted everytime we run rake test_app.

Configure TravisCI for our extension

If we need a CI for our extension, we can simply configure TravisCI. The only extra configuration required to let it handle correctly an extension is related to the rake test_app command that we have to execute before running our tests. Here it is a sample .travis.yml file:

before_install: gem install bundler --pre
before_script:
  - "bundle exec rake test_app"
script: "DISPLAY=:99.0 bundle exec rspec spec"
notifications:
  email:
    - [email protected]
branches:
  only:
    - master
rvm:
  - 1.8.7
  - 1.9.2
  - 1.9.3

Conclusion

We want to get straight to the point: tests saved us a lot of time developing our first extension, expecially when we have to test orders. Imagine testing a critical feature that depends on checkout; every time you change something in the code you should test an order with guest checkout, an order for an already logged in user, an order for a user that signs up during the checkout, an admin order completion and probably other use cases we've not considered. Making things work without tests would surely be a pain.

Useful resources

You may also like

Let’s redefine
eCommerce together.