[Rspec-devel] BDD with Rails, deciding on context boundaries

Luke Redpath luke at agileevolved.com
Tue Aug 15 08:41:39 EDT 2006


Hi Everybody

I've been working with RSpec on my latest project and I'm currently  
writing a 5 part article about testing Rails apps properly and I plan  
on mentioning BDD and RSpec. First of all though, I want to clear up  
some of my own confusion. I believe *may* have been focussing on  
testing state in my specs rather than behaviour and I'd like advice  
on how to improve what I'm doing if I am indeed going in the wrong  
direction.

Let me focus on model tests for the moment. Lets take a User model. I  
find that the first few contexts for an ActiveRecord model in Rails  
tend to be the same, generally a "New foo" and a "Saved foo". I try  
to avoid testing built-in AR features such as find/destroy/create as  
this should be covered by AR's own tests. Here's some specs for a  
User model on my current project:

http://svn.lazyatom.com/public/plugin_repository/branches/ 
community_features/spec/models/user_spec.rb

Does this look like the right approach to take? If not how could  
these specs be better - more BDD-like? Instead of this:

context "New user" do
   specify "should not be valid" do
     @user.should_not_be_valid
   end
end

Should I be doing something more like this:

context "New user" do
   specify "should return false when calling valid" do
     @user.should_not_be_valid
   end
end

I guess the main difference between the two is the first looks more  
like a user story whereas the second looks more like an explicit test  
of the code's behaviour (rather than the behaviour of the conceptual  
model itself). Which is more correct?

The next thing I want to discuss is controller specs. First of all, I  
think a Rails controller is far too wide in scope to focus on as a  
whole - its always irked me that Rails' unit test stubs for  
controllers (*ahem* sorry *functional* tests if we believe Rails'  
terminology) generate a stub testcase for the entire controller. I  
instead prefer to concentrate on the individual actions.

So lets take, for example, a UserController#create action. Now how  
can we break this down into contexts? One way of looking at  
controller actions is a conceptual "object" that you send "messages"  
to, like normal objects, except in this case the "object" is really  
just a controller method and the "messages" are HTTP Verbs and  
parameters. So would we split the contexts up by verb? But what about  
different results - a create could be successful or it could fail.  
Where does this fit into things? One way of breaking it up would be  
like this

User creation via GET
- should never create a user
- should flash an "invalid request" message
- should redirect back to the previous page

Successful user creation via POST
- should create a new user
- should flash a success message
- should redirect to the user list page

Failed user creation via POST
- should not create a new user
- should flash an error message
- should redirect back to the new user page

Would this be how you would break it down? Also, do you believe these  
specs are the place to specifiy the behaviour of the resulting view  
as well as the action behaviour? Or should the view behaviours have  
their own specs entirely? Or is test::unit and rspec the wrong place  
to try and test your view output - would an acceptance testing tool  
like selenium or watir be more suited to the task?

Finally, what are your thoughts on simulating the above situations  
like successful/failed creations by stubbing out ActiveRecord methods  
like save() (the Stubba library that comes with Mocha -  
mocha.rubyforge.org - is particularly good at doing this)? I feel  
that by simulating successful/failed requests by passing the  
appropriate parameters, you are coupling your controller tests to  
your model's implementation - i.e. if what makes a model valid  
changes, you also need to change your controller tests, which seems  
like a bad smell to me.

What would be really nice is to use mocks so that your model *and*  
controller tests never touch the database. Believe it or not,  
ActiveRecord doesn't make this easy - ActiveRecord barfs if you send  
it a mock when it expects another ActiveRecord object - it explicitly  
type checks. So much for duck typing!

Thanks for listening to my semi-coherent ramblings!

Cheers
Luke Redpath


More information about the Rspec-devel mailing list