[rspec-users] (rspec2/rails3) spec'ing the details of a controller that is not purely *skinny* by design.

Wincent Colaiuta win at wincent.com
Fri Jul 9 09:38:20 EDT 2010


El 09/07/2010, a las 14:29, Frank J. Mattia escribió:

>>> it "should explicitly set created_by" do
>>>  controller.stub(:current_user) { mock_user }
>>>  mock_order.should_receive(:created_by=).with(mock_user)
>>>  post :create
>>> end
>> 
>>> This is my newly working spec. Does this look well thought out or is
>>> there some glaring pitfall to doing it this way?
>> 
>> Well, this is the way mocking and stubbing works. If you want to set an expectation on an object that doesn't exist yet at the time you're setting up the spec, you have to "chain" things so as to inject a mock of your own at the right place, which you've done here. The amount of work you have to do setting this up will vary from case to case.
>> 
>> This is one of the costs of the interaction-based approached, and you have to weigh up that cost against the benefits.
> 
> Out of curiosity, how would you do this with a state based approach?
> I've tried checking the value of created_by after post :create but my
> assign(:order) has no idea what :created_by is.
> assigns(:order).created_by.should eq(mock_current_user) just doesn't
> do what I thought it would.

Ok, some qualifications:

- I use factories heavily (in this example Factory Girl) to make it easier to write tests. I don't worry about the slowness of using real model instances until the spec suite actually becomes large enough for it to be a problem. I also don't worry about the risk of cascading failures (broken model breaks controller spec) because I value the simplicity that comes with keeping mocking and stubbing to a minimum in the specs, and I am wary of the insulation that mocking and stubbing bring and can actually hide real failures (eg. API changes but spec keeps passing anyway). I still do mock and stub, but generally only where it is not easier to just verify state (and it often is easier).

- I use RR for mocking because I like the syntax. Even in a state-based approach, you'll see I have to stub out the "current_user" method on the controller.

- I use a "before" block that sets up @user, @params, and does the stubbing, so that I can reuse the same stuff in many different "it" blocks.

- And I try to keep each "it" block as short as possible, just do the post then inspect the state afterwards. In this case the state that I am checking for is the externally visible stuff like what got assigned (assigns), what got rendered (render_template) or redirected (redirect_to), what flash or cookies got set, and so on. If need be, I can query the database.

- And finally, note that this is written for Rails 3/RSpec 2, which I've been using solidly for the last month and have now forgotten what Rails 2/RSpec 1 specs looked like!

So with all that said, this is more or less what my state-based approach would look like:

  before do
    @user = User.make!
    stub(controller).current_user { @user }
    @params = { :post => { :title => 'foo', :body => 'bar' } }
  end
  
  it 'should set created_by' do
    post :create, @params
    assigns[:post].created_by.should == @user
  end
  
  ...

The controller/action is treated as a "black box" which I never look inside. Each "it" block basically just follows this pattern:

  1. Feed params into the black box
  2. Check _one_ piece of state afterwards

By trying to keep only one "should" assertion in each "it" block I get nice granularity in the event of failure.

It is not "better" nor "the right" way, it is just "a" way of doing it. I've also written controller specs where I ended up mocking left, right and center, but if I can take the state-based approach I generally prefer it. The app I'm currently working on has about 3,000 examples, and the suite runs fast enough that I don't yet feel the need to change my emphasis away from state-based testing.

Cheers,
Wincent



More information about the rspec-users mailing list