RSpec has come a long way since I last used it in anger. Today, I’m
starting through a worked example on test-driving a Ruby on Rails
controller with RSpec, Capybara feature specs, and plenty of mocking.
Along the way, we’ll see some neat new features of RSpec in action.
To my mind, Ruby on Rails controllers are a simple beast in the
Model-View-Controller (MVC) pattern. They simply:
take an HTTP action (GET, POST, PUT or DELETE) for a particular
resource. The mapping between the HTTP verb, the resource, and the
corresponding action is handled above the controller, at the routing
layer. By the time it gets down to the controller, we already know what
the desired action is, and we have an identifier for the resource, where
applicable.
communicate the intent of the action to the underlying model.
return an appropriate response, whether that’s rendering a template with
particular information, or sending back an HTTP redirect.
There are two possibilities when it comes to communicating the intent of
the action to the underlying model:
It can query the model for some information, usually to render it as an
HTML page, or emit some JSON representation of the model; or
it can send a command to the model, and return an indication of the
success or failure of the command.
That’s it. The controller is merely an orchestration layer for
translating between the language and idioms of HTTP, and the underlying
model. I should probably note at this point that the underlying model
isn’t necessarily just a bunch of ActiveRecord::Base-inherited models,
it can be composed of service layers, aggregate roots, etc. The point is
that the controllers are “just” about communicating between two other
parts of the system (at least that’s how I always interpreted Jamis
Buck’s famous Skinny Controller, Fat Model post).
That’s handy, because it makes them nice and easy to test. I’ve been
brushing up on RSpec over the past couple of weeks, learning all about
the new syntax and features, and applying them to a couple of demo
projects. I thought I’d share what I’d learned about rspec in the form
of a worked example. Get indepth Ruby On Rails Training here
http://mindmajix.com/ruby-on-rails-training
We’ve got a requirement to keep track of Widgets. Widgets are pretty
simple things: they have a name, and they have a size in millimetres
(you wouldn’t want to get different sized widgets mixed up!). Our
client, who manufactures these widgets, is looking for a simple web app
where she can:
list all widgets;
create a new widget; and
delete an existing widget.
We’ve been developing their in-house stock management system for a
while, so we’ve got an established Ruby on Rails project in which to
hang the new functionality. It’s just going to be a case of extending it
with a new model, controller, and a couple of actions. And we’re all set
up with rspec, of course. Better still, we’re set up with Guard, too, so
our tests run automatically whenever we make a change. You can find the
starting point of the project on the initial setup branch.
Listing all the widgets
Let’s start with an integration test. I like the outside-in flow of
testing that BDD encourages (in particular, the flow discussed in The
RSpec Book), where we start with an integration test that roughly
outlines the next feature we want to build, then start fleshing out the
functionality with unit tests. So, our first scenario, in
spec/features/widgets_spec.rb:
RSpec.feature ‘Managing widgets’ do
All our subsequent scenarios will be inside this feature block.
scenario ‘Finding the list of widgets’ do
visit ‘/’
click_on 'List Widgets'
expect(current_path).to be('/widgets')
end
end
I always like to start simple, and from the user’s perspective. If I’m
going to manage a set of widgets, I need to be able to find the page
from another location. In this scenario, we’re checking that there’s a
‘List Widgets’ link somewhere on the home page. It saves that
embarrassing conversation with the client where you’ve implemented a new
feature, but she can’t find it.
The first thing to note is that, by default, rspec now no longer creates
global methods, so Capybara’s feature DSL method (and RSpec’s describe,
which we’ll see in a minute) are both defined on the RSpec namespace.
Perhaps the other thing to note is that I’m just using RSpec feature
specs – with Capybara for a nice DSL – instead of Cucumber, which I’ve
advocated in the past? Why? It turned out that people rarely read my
Cucumber stories, and those that did could cope with reading code, too.
RSpec features are more succinct, consistent with the unit tests, and
have fewer unwieldy overheads. You and your team’s mileage may, of
course, vary!
Finally, there’s rspec’s new expect syntax. Instead of adding methods to
a global namespace (essentially, defining the should and should_not
methods on Object), you have to explicitly wrap your objects. So, where
in rspec 2.x and older, we’d have written:
current_path.should eq(‘/widgets’)
(which relies on a method called should being defined on your object)
instead we now wrap the object under test with expect:
expect(current_path).to eq(‘/widgets’)
It still reads well (“expect current path to equal /widgets” as opposed
to the older version, “current path should equal /widgets”). Combined
with the allow decorator method, it also gives us a consistent language
for mocking, which we’ll see shortly. To my mind, it also makes it
slightly clearer exactly what the object under test really is, since
it’s (necessarily) wrapped in parentheses. I like the new syntax.
Let’s just assume we’ve implemented this particular scenario, by
inserting the following into app/views/layouts/application.html.erb:
RSpec.describe ‘WidgetsController’ do
it ‘routes GET /widgets to widgets#index’ do
expect(get: ‘/widgets’).to route_to(‘widgets#index’)
end
it ‘generates /widgets from widgets_path’ do
expect(widgets_path).to eq(‘/widgets’)
end
end
In order to make these specs pass, add the following inside the routes
block in config/routes.rb:
resources :widgets, only: [:index]
But one of our specs is still failing, complaining about the missing
controller. Let’s create an empty controller to keep it happy. Add the
following to app/controllers/widgets_controller.rb:
class WidgetsController < ApplicationController
end
We’ve still got a failing scenario, though – because it’s missing the
index action that results from clicking on the link to list widgets.
Let’s start to test-drive the development of that controller action.
This is what I describe as a “query”-style action, in that the user is
querying something about the model and having the results displayed to
them. In these query-style actions, there are four things that typically
happen:
the controller asks the model for some data;
it responds with an HTTP status of '200 OK’;
it specifies a particular template to be rendered; and
it passes one or more objects to that template.
Let’s specify the middle two for now. I’ve a feeling that will be enough
to make our first scenario pass. In
spec/controllers/widgets_controller_spec.rb, describe the desired
behaviour:
RSpec.describe WidgetsController do
describe ‘GET index’ do
def do_get
get :index
end
it 'responds with http success' do
do_get
expect(response).to have_http_status(:success)
end
it 'renders the index template' do
do_get
expect(response).to render_template('widgets/index')
end
end
end
Defining a method to perform the action is just one of my habits. It
doesn’t really add much here – it’s just replacing get :index with
do_get – but it helps to remove repetition from testing other actions,
and doing it here too gives me consistency amongst tests. Now define an
empty index action in WidgetsController:
def index
end
and create an empty template in app/views/widgets/index.html.erb. That’s
enough to make the tests pass. Time to commit the code, and go grab a
fresh coffee.
Next up, let’s specify a real scenario for listing some widgets:
scenario ‘Listing widgets’ do
Widget.create! name: ‘Frooble’, size: 20
Widget.create! name: ‘Barble’, size: 42
visit ‘/widgets’
expect(page).to have_content(‘Frooble (20mm)’)
expect(page).to have_content(‘Barble (42mm)’)
end
Another failing scenario. Time to write some code. This time it’s
complaining that the widgets we’re trying to create don’t have a model.
Let’s sort that out:
rails g model Widget name:string size:integer
We’ll tweak the migration so neither of those columns are nullable (in
our domain model it isn’t valid to have a widget without a name and a
size)
class CreateWidgets < ActiveRecord::Migration
def change
create_table :widgets do |t|
t.string :name, null: false
t.integer :size, null: false
t.timestamps null: false
end
end
end
Run rake db:migrate and we’re good to go again. Our scenario is now
failing where we’d expect it to – the list of widgets are not appearing
on the page. Let’s think a bit about what we actually want here. Having
discussed it with the client, we’re just looking for a list of all the
Widgets in the system – no need to think about complex things like
ordering, or pagination. So we’re going to ask the model for a list of
all the widgets, and we’re going to pass that to the template to be
rendered. Let’s write a controller spec for that behaviour, inside our
existing describe block:
let(:widget_class) { class_spy(‘Widget’).as_stubbed_const }
let(:widgets) { [instance_spy(‘Widget’)] }
before(:each) do
allow(widget_class).to receive(:all) { widgets }
end
it ‘fetches a list of widgets from the model’ do
do_get
expect(widget_class).to have_received(:all)
end
it ‘passes the list of widgets to the template’ do
do_get
expect(assigns(:widgets)).to eq(widgets)
end
There are a few interesting new aspects to rspec going on here, mostly
around the test doubles (the general name for mocks, stubs, and spies).
Firstly, you’ll see that stubbed methods, and expectations of methods
being called, use the new expect/allow syntax for consistency with the
rest of your expectations.
Next up is the ability to replace constants during the test run. You’ll
see that we’re defining a test double for the Widget class. Calling
as_stubbed_const on that class defines, for the duration of each test,
the constant Widget as the test double. In the olden days of rspec 2,
we’d have to either use dependency injection to supply an alternative
widget implementation (something that’s tricky with implicitly
instantiated controllers in Rails), or we’d have to define a partial
test double, where we supply stubbed implementations for particular
methods. This would be something along the lines of:
before(:each) do
allow(Widget).to receive(:all) { widgets }
end
While this works, it is only stubbing out the single method we’ve
specified; the rest of the class methods on Widget are the real, live,
implementations, so it’s more difficult to tell if the code under test
is really calling what we expect it to.
But the most interesting thing (to my mind) is that, in addition to the
normal mocks and stubs, we also have test spies. Spies are like normal
test doubles, except that they record how their users interact with
them. This allows us, the test authors, to have a consistent pattern for
our tests:
Given some prerequisite;
When I perform this action;
Then I have these expectations.
Prior to test spies, you had to set up the expectations prior to
performing the action. So, when checking that the controller asks the
model for the right thing, we’d have had to write:
it ‘fetches a list of widgets from the model’ do
expect(widget_class).to receive(:all)
do_get
end
It seems like such a little thing, but it was always jarring to have
some specs where the expectations had to precede the action. Spies make
it all more consistent, which is a win.
We’re back to having a failing test, so let’s write the controller
implementation. In the index action of WidgetsController, it’s as simple
as:
@widget = Widget.all
and finish off the implementation with the view template, in
app/views/widgets/index.html.erb:
All Widgets
-
<%= render @widgets %>
<%= content_tag_for :li, widget do %>
<%= widget.name %> (<%= widget.size %>mm)
<% end %>