On 2010-07-09 8:38 AM, Wincent Colaiuta wrote:
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.
I'm curious, Wincent, what you consider to be "fast enough"? You have
about twice as many examples as I have in a project, and I'm starting to
think that it's taking too long to run the whole suite. I do tons of
mocking and stubbing (and grow very weary of it), but can't imagine how
long my suite would take if I didn't. Currently, if I run with RCov,
it's in the 6-7 minute range. Without RCov, it's between 5-6 depending
on whether it's running in Ruby 1.8.7 or 1.9.1 (1.9.1 being faster). I
am using PostgreSQL in testing, though, so I know that's adding a bit. I
was using SQLite in testing until recently, and that was still up over
3-4 minutes. [I switched to PostgreSQL in testing so I could take
advantage of PostgreSQL specific features. There is zero chance that
this app will ever need to run on anything other than PG, so portability
is not a concern.] There are a couple of tests that take quite a bit of
time (one generates a PDF and verifies that it is where it is supposed
to be and another creates a bunch of records to calculate statistics
for), but even without them, the suite is taking over three minutes.
So how long do your 3k examples take?
Peace,
Phillip
_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users