[rspec-users] [newbie]: How to test for operations inside a block?

Mark Wilden mark at mwilden.com
Tue Sep 23 12:38:16 EDT 2008

On Tue, Sep 23, 2008 at 3:39 AM, CarmineM <carmine.moleti at gmail.com> wrote:

> I came up with this code:
>   def mock_result(stubs = {})
>     @result ||= mock('Object', stubs)
>   end
>    describe "being an administrator" do
>       it "should create a new user using OpenID authentication" do
> @controller.should_receive(:authenticate_with_open_id).with(@identity_url,
> anything()).and_yield(mock_result(:successful? => true),
> @identity_url, mock_registration)
>        User.should_receive(:create_with_role).and_return(@user =
> create_operator_user)
>        do_verb
>         response.should redirect_to(users_path)
>      end
>     end
> which doesn't give any error and seems pretty fine to me. Is this the
> way it is meant to be done?

Without knowing everything about your code, that looks almost there! You're
basically specing that when the create action is called (from do_verb):

1. authenticate_with_open_id should get called with certain parameters
2. When it does get called, have it yield certain other parameters to its
3. User.create_with_role gets called and returns something returned by

I would ask:

1. What arguments should User.create_with_role be called with? These should
be specified (tested).
2. What is the return value of User.create_with_role, and what should be
done with it? Answer: it returns a user, which should end up getting
assigned to the controller's @user. This specification is accomplished with
assigns[:user].should == <the mock user object you're returning from

As far as "the way" to use RSpec in situations like this, I use TDD/BDD.
Basically, this means that we don't write any code without a failing test.
Think about that. It's a pretty weird concept. But it works.

One way to achieve this ex post facto would be to comment out all your
controller code. Write the simplest spec that describes something you want
to have happen. It will fail. Then comment back in the code (and just the
code) to make the test pass. After it passes, see if the commented in code
expresses its intent well (in other words, clean it up if necessary). Then
think of another test (or "description of behavior")  to write. It fails,
you make it pass, you clean it up. And so on. Continue this cycle until you
can't think of any more tests - in other words, everything you want the code
to do has been described/verified/tested.

This has several benefits: 1) You take tiny steps, which are so much easier
than any other kind of steps. 2) You make sure you haven't missed any
specifications, because every line of code is described before it's even
been written, and 3) You move away from thinking "is the code I just wrote
correct?" to "what should this code I'm about to write actually accomplish"?

Also, try to match RSpec's grammar with regard to the "describe" and "it"
blocks, such that they stand alone as documentation (though I can never
remember the RSpec command to produce it). In the above spec, you're saying
"being an administrator, it should create a new user using OpenID
authentication." In reality, what you want to describe is what happens when
someone POSTs data to your create action. That may have further context
applied, such as "when the poster is an administrator".

What line of code does all this describe? The do_verb call. So if I were
writing this from scratch, that's the first line of code I'd write in the
spec (presumably, something like 'post :create, :identity_url => 'asdf'').
I'd run this line of code and it would fail (I think) because that action
doesn't exist in the controller (because we commented it out). So the next
step would be to do the simplest thing that could make the test pass;
obviously, defining the controller method (without anything in it).

What should the next test be? Well,  you could test that
authenticate_with_open_id is called. So you'd set up an expectation on that
method: @controller.should_receive(authenticate_with_open_id). You'd run the
test, it would fail, and then you'd add the call. Now, what parameters
should authenticate_with_open_id receive? Specify them. Test fails. Add
them. Test passes.

If you go through this process with every single line of code you write, not
only do the tests become obvious as you go along, but so does the code under
test. You might find that (as in your original code), you have to set up a
lot of tests on what happens to the new user. As a result, you might realize
that the controller shouldn't care about these details, and that you should
write User.create_with_role (for which you'll eventually write its own
tests). This is an example of how when testing is tedious or difficult, it
may point to the code not being structured correctly.

Good lord; listen to me rattle on. :) I'm pretty confident in everything
I've said here, but it's been explained a lot better by others. Perhaps the
best way to get more information on how to use RSpec is to google TDD and/or
BDD. And stay on this list, paying especially attention to posts by people
like David and Pat (as well as others).

Good luck!

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080923/eb8bfea4/attachment.html>

More information about the rspec-users mailing list