Back in part 1, we had a look at some of the new features of RSpec, and we used those features to create a query-style controller action. In particular, it listed out all the widgets in our inventory management system. This time around, we’re going to look at a command-style action: creating a new widget.
What do I mean by a “command”-style action, though? I think of these as the kinds of actions where the user is telling the system to do something: create a widget, delete a widget, or send an email, for example. There might be a more complex workflow, too: creating a new user, adding them to an announcement mailing list, and sending them an activation email to confirm their address. That sort of behaviour could be encapsulated by a service object which performs all the actions associated with the business requirements for creating a new user.
They still follow a common pattern:
-
the controller looks up the model on which an action has to be performed, if appropriate;
-
then it tells the model to perform that action, noting the success or failure of the action.
Then we branch in two, depending on whether the model operation was successful. If it was:
-
redirect to another action – often the canonical url for the resource; and
-
set a flash message to let the user know that the operation completed successfully.
If the operation wasn’t successful, then:
-
respond with an appropriate HTTP response code. Somewhat counterintuitively, if this is a web-based form a human is filling in, it will be a
200 OK
. If it’s an API, then return an appropriate 4xx response code. -
re-render the template the user didn’t fill in correctly, passing the appropriate object(s) to that template.
Let’s see this in action. We’ll start out by writing an integration test for creating widgets. First of all, let’s make sure we can find the button:
and implement that by adding the appropriate link on the widget listing page:
Now our tests are failing all over the place, complaining about new_widget_path
not existing. We should write a couple of routing specs to check that works:
and, finally, changing the line in config/routes.rb
to include the new action, too:
Running our tests reveals that all the other tests are back to passing again (phew!), but that our scenario is still failing, this time because the new
action is missing. Let’s start test-driving the implementation of that. The new
action is just another query-style action, so I’ll gloss over it quickly:
You start to get a feel for the pattern, right? It all fits in a screenful of
code, too. The one other change I made was to pull the stubbing of the Widget
class out into the enclosing describe block, so it’s available to every nested
describe block.
I have a confession to make here. The point of test-driven development is that
you write a single failing test, then write just enough code to make that test
pass. Wash, rinse, repeat. However, I did just write all four of these tests at
once, resulting in four failing tests. The thing with rules is knowing when
it’s OK to break them, right? In this case, I already know the implementation is trivial, with the following new
action:
and the view in app/views/widgets/new.html.erb
:
and, finally, the form in app/views/widgets/_form.html.erb
:
(I still love that you can express forms so simply, while using a FormBuilder
subclass to express the tricky HTML gymnastics required to make forms look
pretty.) If it turns out that I’d been over-eager, and I still had failing
tests at this point, I’d backtrack a bit, remove (or comment out) some of the
tests, and choose a smaller thing to make the tests pass. But it turns out
that’s enough to make our controller tests pass, and the initial scenario
passes, too. Time for a coffee, then onto the real meat: the create
action.
We’ll start with the happy case: a correctly entered, valid, widget:
Capybara has a nice, expressive, DSL doesn’t it? I’d like to think that a non-programmer could – with some effort – read and understand what’s going on. (But I’m a programmer, and it’s pretty much my natural language, so I might be way off!)
The scenario is failing, because it can’t find the appropriate routes. Let’s write a test for that route (the url generation for widgets_path
has already been covered):
So now we modify the routes to include the create action:
and our integration test is now pointing at the next place we need to work on: the create action is missing. Let’s write some tests for the common behaviour between the happy path and the error case:
It turns out that the controller has an additional responsibility after all. As of Rails 4.mumble it now figures out which parameters are valid for this request, so we’ve got a wee spec in there to make sure it’s filtering out invalid (or malicious) data.
There’s a (perfectly valid) argument against stubbing out the behaviour
underneath the controller here. What happens when you stub out the
implementation you expect, write just enough code to make the tests pass, then
discover it doesn’t work in production because the behaviour you stubbed out
doesn’t actually exist? In the example above, how do I know that there’s a
Widget.create
method that takes a hash? Fortunately, RSpec 3 has our backs,
with “verifying doubles”. These doubles will, if the object they’re standing in
for exists, verify that there really is a method which takes the same number of
arguments. Lets fat-finger our before block:
Running our tests reveals our mistake:
The slight snag is that the object being stubbed needs to be around when it is being stubbed, so that rspec can verify the methods really exist. In practice, this means that you’ll only see this sort of feedback when you run your entire test suite, but not necessarily when (for example) Guard is only running the tests that have changed. Still, it’s useful feedback, and another great feature from RSpec 3!
So, what happens on the happy path?
The before block here is unnecessary – we’ve already stubbed out the
persisted?
method to return true – but it helps me to make the context of
the describe block explicit. Let’s be bold and write some tests for the failure case, too:
and we can write a simple implementation:
Done, our unit tests are passing and, better still, it’s been enough to make the integration test pass, too! Winning. :) Time to commit, push to production, and treat ourselve to a takeout pizza for dinner. As with part 1, you can find the code on GitHub, on the part 2 branch. Tomorrow, let’s have a look at how to test our model – right now, since we don’t have an integration test to demonstrate it, we don’t know that widgets without a name do the right thing (I can honestly say I haven’t tried this project in the browser, but I’m confident that submitting a widget without a name is going to raise an exception, which isn’t the sort of behaviour our client would like.)