RSpec: It Should Behave Like
16 comments Latest by John Yerhot Thu, 21 Aug 2008 20:27:52 GMT
I was going through an older project of ours and cleaning up some specs and noticed how often we were doing the same thing in several places. When we started the project, we didn’t get the benefits of shared groups. Now that we have some time to go through and update some of our older specs, I’ve been trying to take advantage of the features currently available in RSpec. One feature that I haven’t seen a lot of mention of by people is shared groups, so I thought I’d take a few minutes to write up a quick intro to using it.
To pick some low-hanging fruit, let’s take an all-too-familiar method, which you might be familiar with… login_required. Sound familiar? Have you found yourself stubbing login_required over and over throughout your specs?
describe Admin::DohickiesController, 'index' do
before( :each ) do
controller.stub!( :login_required )
Dohicky.should_receive( :paginate ).and_return( Array.new )
get :index
end
...
endIf you’re requiring that a user should be logged in when interacting with most of the application (as in the case of an administration section/namespace), you might want to consolidate some of your work into one shared specification group. The basic premise behind this is that you can write a typical describe block and load it into any other spec groups that you need. For example, in our case, we’ll need to stub login_required in several places. We can set this up in one shared group and reference it wherever necessary.
For example, here is what we’ll start off with.
describe "an admin user is signed in" do
before( :each ) do
controller.stub!( :login_required )
end
end
describe Admin::DohickiesController, 'index' do
...However, the new describe block isn’t accessible from the block at the bottom of the example… yet. To do this, we just need to pass the option: :shared => true as you’ll see in the following example.
describe "an admin user is signed in", :shared => true do
before( :each ) do
controller.stub!( :login_required )
end
endGreat, now we can reference it by referring to it with: it_should_behave_like SharedGroupName. In our example above, this would look like:
describe "an admin user is signed in" do
before( :each ) do
controller.stub!( :login_required )
end
end
describe Admin::DohickiesController, 'index' do
it_should_behave_like "an admin user is signed in"
before( :each ) do
Dohicky.should_receive( :paginate ).and_return( Array.new )
get :index
end
...
end
describe Admin::DohickiesController, 'new' do
it_should_behave_like "an admin user is signed in"
before( :each ) do
@dohicky = mock_model( Dohicky )
Dohicky.should_receive( :new ).and_return( @dohicky )
get :new
end
...That’s it! Pretty simple, eh? We can now reference this shared group in any describe blocks that we want to. A benefit to this approach is that we can make change the authentication system (say, we decide to switch it entirely and/or even just change method names, set any other prerequisites necessary when an admin is signed in), we’ll have a single place to change in our specs. (tip: you can put these in your spec_helper file)
You can learn more about it_should_behave_like and other helpful features on the RSpec documentation site.
If you have any suggestions on better ways of handling things like this, please follow up and share your solutions. I’m always looking to sharpen my tools. :-)
Update
In response, Bryan Helmkamp suggests that a better solution is to define a method in our specs like, for example: build_mock_user_and_login. then calling it in our before(:each). So, maybe the approach above isn’t the most ideal method but I did wantt o draw some attention to it_should_behave_like. I suppose that I need a better example.. another post, perhaps? :-)
Also, Ed Spencer has posted an article titled, DRYing up your CRUD controller RSpecs, which will introduce you mor to it_should_behave_like.
Thanks for feedback people!
Related Posts
Enjoying the content? Be sure to subscribe to my RSS feed.






I’d +1 this approach too :)
When defining the shared example, you can also use:(think this may be the preferred way ?)
... or, a funkier, more “ruby” like way, ala:... where you can include that like:
Also, it sometimes helps to create your mocks in them as well, like:
share_examples_for "Logged In Admin" do controller.stub!( :login_required ) @mock_admin_user = mock_model(User, {:is_admin => true}) endShared Behaviours FTW !
I’m not a big fan of this usage of shared examples. Expanded, here’s what the code in your example says:
“Admin::DohickiesController#new should behave like an admin user is signed in.”
The problem is it’s taking what’s context and putting it in the place of an outcome (as in, the piece after the should).
That said, I agree that duplicating the stub for login_required all over is not ideal because you might refactor to a different login system later. I usually handle this by defining a method in my specs like “build_mock_user_and_login”.
If all I care about is being logged in, I can call that from the before(:each) or inside each example. The method returns a mock_model, so I can also capture the result of it and use that object in the remainder of the example. One nice thing about this approach is that if I refactor away from login_required, I only have one place to update for all the specs that aren’t specifically concerned with login.
You do have to repeat the call to build_mock_user_and_login, but that duplication isn’t really any worse than duplicating “it_should_behave_like ‘an admin user is signed in’”. Both are essentially declarative.
Cheers,
-Bryan
Glad to see you posting some more, any further RSpec tips are especially appreciated!
I normally also go with just defining a method, it not ‘as’ readable but you can pass arguments and they are easy to find via method-lookup(eclipse).
Conveniently there’s the controller instance you can get to in (shared) controller specs. Is there something similar built in for model specs? Or do I have to define e.g. a get_instance method in the actual model spec (which includes the it_should_behave_like ..) to get an instance of the model in the shared spec?
Cheers.
We generally add a login_user method to spec_helper. Then we can use that anywhere we need it. Similarly for login_admin_user.
I agree with Bryan, and personally think that shared examples can be a good substitute for always having to use the before block for everything. For example, using them for before_filters.
Once you test the before_filter in isolation, you should be free to stub it out in other tests, so that those tests are then being tested in isolation. It reads well too.
In regard to using them for the logged_in example, I’d personally opt for showing that within the actual request. Here’s an example of how I would usually do this fwiw:
shared_examples_for "Green Dohicky In Context" do before @mock_green_dohicky = mock_model(Dohicky, {:color => 'green'}) @mock_green_dohicky.should_receive(:aoeu).and_return(:stnh) end describe DohickiesController do describe "responding to GET /new" describe "as an admin user" do it_should_behave_like "Green Dohicky In Context" before do Dohicky.stub!(:paginate).and_return(Array.new) end def do_get logged_in_as_admin { get :new } end it "should return an array from a call to Dohicky.paginate" do before_get { Dohicky.should_receive(:paginate).and_return(Array.new) } end it "should do something with the green dohicky" do during_get { @mock_green_dohicky.should do_something } end it "should do something else with the green dohicky" do ... end end describe "as a non logged in user" do it_should_behave_like "Green Dohicky In Context" ... end end endBut yeah, would usually test the before_filters specific to that controller in that test, and push the global shared_examples out to spec_helper or another module.
Oh… and I also use this awesomeness (that should be a word) for cleaning up some of the scope fun… ie: making the request in the right place: http://gist.github.com/6343
What do you think ? That’s my “seems-to-be-ever-changing” controller approach at the moment ;)
Nice to see this feature getting some exposure, in fact you inspired me to write up how I handle shared example groups when testing CRUD actions. Shared Example Groups are an awesome but underused feature of the RSpec library.
One minor point though – you’re currently putting the expectation that Dohicky.should_receive( :paginate ).and_return( Array.new ) in your before(:each) block. It might just be a matter of one style vs another but I would probably use stub! there and put the expectation into an it “should” do … end block, as you’re currently making the same expectation many times over.
I think you’re also missing the :shared => true at the top of your last code example.
I think this is one of the most underused aspects of Rspec, and it’s so powerful. I only discovered it because a colleague refactored some tests when I was on holiday, so the more you can do to get the news out there the better ;)
@Bryan,
Thanks for sharing your thoughts on using a method to do this as well. I’ll have to play around a bit and see how readable I can make this, which is one of my goals when writing specs.
@ander:
Yep, you can use it_should_behave_like in model specs as well.
@ed:
Great tutorial, I’ll have to give that a whirl in the near future.
One caveat is that if a shared spec fails, you don’t see it in the call stack.
David Chelimsky suggested using—backtrace, but I never got around to trying it.
I was trying to point out that shared controller specs have easy access to the controller by just saying “controller”, whereas shared model specs apparently have to do some custom stuff to get to the model.
Here are some thoughts: http://exceptionisarule.blogspot.com/2008/08/rspec-shared-model-specs.html
What I was trying to say is that shared controller specs have an easy access to the controller by just saying “controller”, whereas shared model specs have to do some custom stuff to get to the model.
Some thoughts: http://exceptionisarule.blogspot.com/2008/08/rspec-shared-model-specs.html
This has been one of the most useful blog postings ever (for me). While I was already using #shared_examples_for to great effect, I was not creating helper methods for my #before blocks. I am currently refactoring a beast of a spec to use these techniques.
Thank you for the post and the comments!
I prefer to put shared spec behavior in a controller macros file. Then you can include that in your spec helper:
Now all the methods in ControllerMacros are available to use in any controller specs.
I’m pretty new to Rspec, but this post and everyone’s comments/thoughts are great. Thanks!