Introduction

Spree, the best framework for building online shops, contains basically everything you need for builidng a customizable, easily extensible, yet still upgradable online store. The official documentation contains almost everything a developer needs to build a Spree-based store, from developer documentation (customizing, extending, using the RESTful API) to user (store administrator).

However, one part of it – Testing Spree Applications – is very lacking: it describes running the test suite of Spree itself, without any instructions on how to cover your brand new Spree-based store with a decent test suite. Which is unfortunate, as any customization or extension can introduce a regression, so the fact that Spree itself is thoroughly covered with tests might not be enough. But lack of docs shouldn’t stop us from writing decent tests; it’s a Rails application after all (albeit with lots of specific code)!

NOTE: the content of this post is submitted as a pull request to Spree guides, hopefully making the previous paragraph obsolete and no longer true.

Throughout this guide we’ll be using the word “test” regardless of the testing framework in use.

Prerequisites

This guide is based on Spree’s current development 3.0.0.beta version, which doesn’t differ at all (with regards to tests) from the current stable 2.4.2 version. Additionally it should work for all 2.x versions, of course after taking into account older versions of test-related gems.

This guide assumes having a working Spree-based application in development mode. This means having done all the setup steps – like adding Spree to Gemfile, mounting engine in routes.rb, copying all the migrations, etc.

Last but not least, this guide assumes working knowledge of testing Rails applications with RSpec, Capybara and a few assorted tools (FactoryGirl, DatabaseCleaner).

Setup

To begin testing your Spree application add some gems to your Gemfile, preferably in the test and development groups (so that commandline utilites are available). We’re going to use RSpec, since this is the standard testing framework used by Spree and its extensions. You can use Test::Unit or minitest, but let’s stick to the standard community choices for now.

Add the following to your Gemfile:

# testing
group :development, :test do
  gem 'rspec-rails', '~> 3.1.0'
  gem 'factory_girl_rails', '~> 4.5.0'
  gem 'capybara', '~> 2.4'
  gem 'database_cleaner', '~> 1.3'
  gem 'email_spec'
  gem 'webmock', '1.8.11'
  gem 'poltergeist', '1.5.0'
  gem 'timecop'
  gem 'ffaker', '~> 1.16'
end

We’re not going to use all of these gems in this guide, but they’re used by Spree’s test suite. Feel free to trim down the above list to only the gems your app’s test suite actually uses.

Time for some standard work in commandline. First bundle install, then rails generate rspec:install.

Edit the generated spec/spec_helper.rb and spec/rails_helper.rb to your liking, we don’t need to add anything Spree-specific there. My personal preference is (at least at this stage) to leave rails_helper intact, except for uncommenting the line that loads spec support files (it’s this one: Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }) and in spec_helper having the following options active:

# ...
config.disable_monkey_patching!
# ...
config.order = :random
Kernel.srand config.seed

Assuming your test database is created and migrated (remember that it should have all the migrations copied from Spree and from extensions), this should be enough to run rake spec and see a proper 0 examples, 0 failures green run text. Can you feel the excitement? Let’s move on!

FactoryGirl

Time to add factory_girl and code necessary to use Spree’s factories in our tests.

Add a file spec/support/factory_girl.rb and put the following content in it:

require 'factory_girl'
require 'spree/testing_support/factories'

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

The second line is all you need for accessing Spree’s factories in your app’s test suite.

First Test: Model

Let’s write a small test to see if it all works together, with Spree’s classes, logic and factories. Add a file spec/models/spree/order.rb with the following content:

require 'rails_helper'

RSpec.describe Spree::Order do
  it "generates an order and advances it to the address state" do
    order = FactoryGirl.create(:order_with_line_items)
    order.next!
    expect(order.state).to eq("address")
  end
end

This test may seem trivial, but can already catch regressions if you customized the Order’s state machine. I have a Spree store which sells digital goods, which means its order_decorator has remove_checkout_step :address and remove_checkout_step :delivery; in this store the above test would fail, as an order from the cart state advances directly to the payment state.

Run it and it should be green. Congratulations, you know how to write tests for a Spree-based store!

Controller Tests

Typical controller tests in Rails are funky: they don’t take into account routes.rb contents since they’re meant to test a given controller’s actions in isolation from the rest of the stack. Usually this is not a problem, but becomes one if you want to have a test for an engine’s controller in your app. Let’s walk through it.

Suppose you’ve customized Spree’s ProductsController somehow, e.g. by adding an extra before_action that changes how the application reacts to a product show request. Let’s write the simplest test to ensure GET /products/:id is still handled properly by Spree. Add a file spec/controllers/spree/products_controller_spec.rb with the following contents:

require 'rails_helper'

RSpec.describe Spree::ProductsController, :type => :controller do
  it "shows an available product" do
    product = FactoryGirl.create(:product, :available_on => 1.year.ago)
    get :show, :id => product.to_param
    expect(response).to be_success
  end
end

Running this test will raise the following error:

Failure/Error: get :show, :id => product.to_param
ActionController::UrlGenerationError:
  No route matches {:action=>"show", :controller=>"spree/products", :id=>"product-1-751"}

Rails’ environment for controller tests does not recognize spree/products, since it doesn’t know we’ve mounted the Spree engine (we’re ignoring routes.rb, remember?) and therefore such a request does not make any sense to it.

Spree made a handy set of convenience methods for controller testing in its TestingSupport::ControllerRequests module. To have them available in our controller tests, add a spec/support/spree_controller_requests.rb file with the following content:

require 'spree/testing_support/controller_requests'

RSpec.configure do |config|
  config.include Spree::TestingSupport::ControllerRequests, :type => :controller
end

And change the get :show, ... line to spree_get :show, ... in the test.

Re-run and… another error!

Failure/Error: spree_get :show, :id => product.to_param
NoMethodError:
  undefined method `authenticate' for nil:NilClass

This time it’s caused by the lack of Devise methods, since Spree checks if a user is logged in even in frontend actions. To fix this, add the line that includes Devise’s test helpers in controller_requests.rb file:

require 'spree/testing_support/controller_requests'

RSpec.configure do |config|
  config.include Spree::TestingSupport::ControllerRequests, :type => :controller
  config.include Devise::TestHelpers, :type => :controller
end

Re-run and… voilà, it’s green! Now you know how to test Spree’s built-in controllers from within your app’s test suite.

Acceptance Tests

Time for the grand finale – acceptance testing your Spree-based store using Capybara.

This is going to be the most pleasant part, as there are no Spree-specific surprises beyond FactoryGirl configuration we did already.

Put these two lines in spec/rails_helper.rb, below the require 'rspec/rails' line:

require 'capybara/rails'
require 'capybara/rspec'

Then add a simple feature spec in spec/features/browsing_products_spec.rb:

require 'rails_helper'

RSpec.describe "browsing Products", :type => :feature do
  before :each do
    FactoryGirl.create(:product, :available_on => 1.year.ago,
      :name => "T-shirt", :slug => "t-shirt")
  end

  it "shows a Product" do
    visit "/products/t-shirt"
    expect(page).to have_content "T-shirt"
    expect(page).to have_content "Add To Cart"
  end
end

And that’s all. You have a green feature test that ensures the whole stack of your Spree-based store works as expected.

Final Remarks

Models, controllers and acceptance (feature) tests should be enough to write decent test suite for your Spree-based application.

This guide only scratched the surface of the extra testing stuff that Spree brings to the table. Do yourself a favor and check out the lib/testing_support directory in Spree’s repository, which contains loads of extra modules to ease writing tests for Spree-based applications and extensions. Yes, extensions – the knowledge you gained from this guide is also applicable to testing Spree extensions, as they’re basically tests for a “dummy Rails app” that includes only Spree and the given extension.

This guide covered only the first three testing-related gems added to the Gemfile. Starting from Capybara they don’t need any extra Spree-related work to be used in our test suite, so their official docs should be sufficient. Remove them from the Gemfile if you’re not going to use them in your app’s tests.

If you want to modify Spree’s factories, check out this blogpost by Benjamin Tan.