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.