Forum: RSpec When to use Factories, Mock Models, Mocks & Stubs

Posted by Frank Lakatos (Guest)
on 2010-02-03 01:11
(Received via mailing list)
Hi guys, been following for about 3 weeks, first question -

I've been spending the last couple of months learning RSpec and
Cucumber and I'm just finally starting to see the "big picture", at
least I think I am. But I've got some questions I was hoping you guys
can clear up. I'm sure this has been asked a lot, so please bare with
me. Just let me know when my logic is wrong or when I'm way off
course. You guys have been a kind group, so I'm not to concerned with
sounding foolish.

I've been using .should_receive and .stub to let my spec know that my
controller is going to be making a method call. I know .should_receive
will fail when you don't call that method (or if you call it more
times than you said you would) and I use stub when It's going to get
called an unpredictable amount of times, like in a before block. To
me, it sounds like I don't really know what the difference is, and I
just have some understanding of there symptoms.

When i need to talk about a model, I usually use mock_model. I have
Factory_girl, but I've heard that you should be careful when using
Factory girl, as it ties you down to the requirements of the
attributes, and not the overall functionality (although, that
statement may not be true in itself). So normally, I use Factories in
Cucumber, as that is concerned with more higher level usage features,
and mock_models for controller/model specs.

Alot of times, however, I feel like I'm doing way to much work to get
the whole thing working. Maybe I am, maybe I'm not, only way is to
show off what I'm doing.

app/controller/projects_controller#create

   def create
       @client = current_user.company.clients.find(params[:project]
[:client_id])
       @project = @client.projects.build(params[:project])
       if @client.save
         flash[:notice] = "Added: #{@project.name}"
       else
         render :new
       end
   end


spec/controller/projects_controller_spec

describe ProjectsController do
     describe "POST 'create'" do

     before do
       @current_user = mock_model(User)
       controller.stub(:current_user).and_return @current_user
       @company = mock_model(Company)
       @current_user.should_receive(:company).and_return @company
       @clients = mock("Client List")
         @company.should_receive(:clients).and_return @clients
     end

     describe "when client is found" do

       before do
         @client = mock_model(Client)
         @clients.should_receive(:find).and_return @client
       end

       describe "on successful save" do

         before do
           @projects = mock_model(ActiveRecord)
           @client.should_receive(:projects).and_return @projects
           @project = mock_model(Project)
           @projects.should_receive(:build).and_return @project
           @client.should_receive(:save).and_return true
           @project.should_receive(:name).and_return "New Project"
         end

         it "should set up the flash" do
           post "create", {:project => {:client_id => 1}}
           flash[:notice].should_not be_nil
         end

        end

     end

   end


end


Let me know how it sounds, and it looks like I'm doing so far

Thanks,
Frank
Posted by Andrei Erdoss (cautaro)
on 2010-02-03 06:55
(Received via mailing list)
Hello Frank,

>From my understanding these are the roles of should_receive and stub.

should_receive checks to make sure that a method or a property is 
called. To
this you can specify the arguments that it gets called (.with()), what 
it
returns (.and_return) and how many times this happens (.once, .twice 
etc).

stub on the other hand is a place holder for functions calls that have 
been
tested already or are Rails defaults, which don't need to be tested. 
stubs
are used in conjunction with mock_models, in order to provide for the
functions or properties that are needed for the code to run, up to the 
test
point.

As far as mock_model and Factory_girl, I can say that mock_models are a 
much
more lightweight structure than Factory_girl object. When testing
controllers I try to use only mock_models. As far as models, I use
Factory_girl only when dealing with the object being tested. The other
complementary objects are mocked by using mock_model.
Posted by Adam Sroka (Guest)
on 2010-02-03 07:15
(Received via mailing list)
On Tue, Feb 2, 2010 at 9:53 PM, Andrei Erdoss <erdoss@gmail.com> wrote:
> are used in conjunction with mock_models, in order to provide for the
> functions or properties that are needed for the code to run, up to the test
> point.
>

I think that it is best to think of these in terms of command query
separation. In case you aren't familiar with that principle, it states
that some methods are commands - they tell an object to do something
but don't return anything interesting, and other methods are queries -
they return some interesting value but have no side effects.

should_receive is how we set an expectation for a command. We don't
really care what a command returns but we do care that it gets called.
should_receive literally says that the command should be called with
the given parameters.

stub is how we handle a query. We care what a query returns, or rather
the code we are testing does, but we don't really care when it gets
called (or how often) per se. If we depend on its result then it
should be called, but the effect that the result has on the system
we're testing is what we really care about.
Posted by J. B. Rainsberger (Guest)
on 2010-02-03 12:45
(Received via mailing list)
On Tue, Feb 2, 2010 at 19:00, Frank Lakatos <me@franklakatos.com> wrote:

> Hi guys, been following for about 3 weeks, first question -

This might help a little: http://bit.ly/ONpXE

To bring things back to Rails, I use mock_model whenever I want to
design controller behavior without relying on the underlying model
behavior. I tend to start using mock_model and mostly stubbing model
behavior, then as controller behavior begins to reveal itself as model
behavior, I push that into the model and mock those methods more
frequently.

I find this rule of thumb helpful: stub unless you're certain to want
to verify this time that the client invoke the server correctly, and
never, never mock multiple methods at once. If you want to mock
multiple methods, you probably have too complex an interaction.

>      end
>      @company = mock_model(Company)
>      end
>        end
>  end
>
>
> end
>
>
> Let me know how it sounds, and it looks like I'm doing so far

Not bad, but I'd probably extract a method for

current_user.company.clients.find(params[:project][:client_id])

and possibly for

@client.projects.build(params[:project])

in order to reduce the number of details that have to go into a single 
spec.

def find_client_for_current_user(client_id)
    current_user.company.clients.find(client_id)
end

def build_new_project(client, project_attributes)
    client.projects.build(project_attributes)
end

def create
    @client = find_client_for_current_user(params[:project][:client_id])
    @project = build_new_project(@client, params[:project])
    if @client.save
        flash[:notice] = "Added: #{@project.name}"
    else
        render :new
    end
end

Now I can write these specs:

stub each of the methods in the first three columns...
find_client | build_project | save || expected_result
valid | valid | true || added project
nil | valid | shouldn't happen || exception (?)
valid | fails | shouldn't happen || exception (?)
valid | valid | false || errors; render new

and finally:

1. stub :find_client to answer mock_model(Client); controller should
receive :build_new_project with the mock model
2. stub :find_client to answer nil; controller should not receive
:build_new_project
3. stub :find_client to answer mock_model(Client); mock model should
receive :save

That's the initial spec list I'd write.
--
J. B. (Joe) Rainsberger :: http://www.jbrains.ca ::
http://blog.thecodewhisperer.com
Diaspar Software Services :: http://www.diasparsoftware.com
Author, JUnit Recipes
2005 Gordon Pask Award for contribution to Agile practice :: Agile
2010: Learn. Practice. Explore.
Posted by Matt Wynne (mattwynne)
on 2010-02-03 15:27
(Received via mailing list)
On 3 Feb 2010, at 11:35, J. B. Rainsberger wrote:

> I find this rule of thumb helpful: stub unless you're certain to want
> to verify this time that the client invoke the server correctly, and
> never, never mock multiple methods at once.

Right, because the mock (should_receive) is an assertion, and it's
usually better to have one assertion per example.

cheers,
Matt

http://mattwynne.net
+447974 430184
Posted by David Chelimsky (Guest)
on 2010-02-03 15:48
(Received via mailing list)
On Wed, Feb 3, 2010 at 8:18 AM, Matt Wynne <matt@mattwynne.net> wrote:
>
> On 3 Feb 2010, at 11:35, J. B. Rainsberger wrote:
>
>> I find this rule of thumb helpful: stub unless you're certain to want
>> to verify this time that the client invoke the server correctly, and
>> never, never mock multiple methods at once.
>
> Right, because the mock (should_receive) is an assertion, and it's usually
> better to have one assertion per example.

Over here to the east of the pond, we say "expectation." :)
Posted by unknown (Guest)
on 2010-02-03 16:01
(Received via mailing list)
Ok, so these ideas seem kind of natural to me, which is nice:

mock_models being used to mock non-tested models
stub for queries and/or well-tested methods, should_receives for 
commands

While reading over Dave Astlels, I kind of got concerned because of
something he states that I feel I'm doing in my specs:

"When you realize that it's all about specifying behaviour and not
writing tests, your point of view shifts. Suddenly the idea of having
a Test class for each of your production classes is ridiculously
limiting. And the thought of testing each of your methods with its own
test method (in a
1-1 relationship) will be laughable."

This is what I am striving for, but being guided simply by rSpec error
messages results me in writing specs like this...

describe "POST 'create'" do

     before do
       @current_user = mock_model(User)
       controller.stub(:current_user).and_return @current_user
       @company = mock_model(Company)
       @current_user.should_receive(:company).and_return @company
       @clients = mock("Client List")
       @company.should_receive(:clients).and_return @clients
     end

     describe "when client is found" do

       before do
         @client = mock_model(Client)
         @clients.should_receive(:find).and_return @client
       end

       describe "on successful save" do

         before do
           @projects = mock_model(ActiveRecord)
           @client.should_receive(:projects).and_return @projects
           @project = mock_model(Project)
           @projects.should_receive(:build).and_return @project
           @client.should_receive(:save).and_return true
           @project.should_receive(:name).and_return "New Project"
         end

         it "should set up the flash" do
           post "create", {:project => {:client_id => 1}}
           flash[:notice].should_not be_nil
         end

       end

     end


   end



... for a controller that looks like this ...


def create
       @client =
current_user.company.clients.find(params[:project][:client_id])
       @project = @client.projects.build(params[:project])
       if @client.save
         flash[:notice] = "Added: #{@project.name}"
       else
         render :new
       end
   end



Am I doing the 1-1 thing that BDD specifically set out to avoid?









Quoting Adam Sroka <adam.sroka@gmail.com>:
Posted by David Chelimsky (Guest)
on 2010-02-03 16:46
(Received via mailing list)
On Wed, Feb 3, 2010 at 8:52 AM,  <me@franklakatos.com> wrote:
> for each of your production classes is ridiculously limiting. And the
>      controller.stub(:current_user).and_return @current_user
>        @clients.should_receive(:find).and_return @client
>      end
>
>      describe "on successful save" do
>
>        before do
>          @projects = mock_model(ActiveRecord)

This is a little odd. @projects is a collection, not a an instance,
and mocking ActiveRecord explicitly seems a bit odd. I'd generally us
a simple array:

@projects = []

>        end
> ... for a controller that looks like this ...
>      end
>  end
>
>
>
> Am I doing the 1-1 thing that BDD specifically set out to avoid?

1-1 example per method is probably a red flag, but 1-1 spec file per
implementation file makes navigation easier, so I think it's actually
a good thing.

The underlying problem with 1-1 mappings stems from IDE's that will
make an empty test case by reflecting on an untested object. You'd end
up with 50 line long test methods named "testGetName" that actually
contain 20 different tests in the one method. That's an extreme, but I
used to see that sort of thing all the time when I was consulting, and
it makes it very difficult to understand what is being tested and what
went wrong when there is a failure.

Make sense?

- David
Posted by unknown (Guest)
on 2010-02-03 17:18
(Received via mailing list)
To say thank you for all your constuctive feedback would not be
enough; all these insights are really helping me to get the provebial
"it".

Dave, I completely agree that the mock_model(ActiveRecord) was
bizzare, but my specs kept failing because @projects was to receive
.build, and it complained it didn't know about it. Using AR fixed
that, but that must have been from a while ago, because I put [] back
in it's place (which i had before) and the spec passes.

I absolutely love the idea of encapsulated the daisy chained calls
(c_u.comp.project) into a controller methods so all i gotta do is stub
that out. So that makes me wonder -- as of right now, I write this
long spec, which looks way more involved than the controller itself.
And, I'd be lying if I said that I thought about my spec first before
writing the app code. Is the idea that I should be trying to think of
a simpler spec, which will force a simpler controller (like
encapsulating functionality into more controller methods)


Once again, thanks++
Frank


Quoting David Chelimsky <dchelimsky@gmail.com>:
Posted by Nicolás Sanguinetti (Guest)
on 2010-02-03 17:38
(Received via mailing list)
On Wed, Feb 3, 2010 at 2:07 PM,  <me@franklakatos.com> wrote:
> I absolutely love the idea of encapsulated the daisy chained calls
> (c_u.comp.project) into a controller methods so all i gotta do is stub that
> out.

Oooh, I hate that one :)

You're adding lots of small methods that actually don't define how the
class should behave, IMO.
In fact, how a client or project is related to a user is a
responsibility of the models, not the controllers.

I would much rather turn

@client = 
current_user.company.clients.find(params[:project][:client_id])
@project = @client.projects.build(params[:project])

into

@client = current_user.find_client(params[:project][:client_id])
@project = @client.projects.new(params[:project])

And add the helper method on the models. Even more:

@project = current_user.add_project(params[:project])

And let the handling of the client and the business rules ("a project
must belong to a client") to the models, as well

Then your controller would be a lot slimmer, and the tests for it much 
simpler.

def create
  @project = current_user.add_project(params[:project])

  if @project.save
    flash[:notice] = "Added '#{@project.name}'"
    redirect_to somewhere
  else
    render :new
  end
end

Cheers,
-foca
Posted by J. B. Rainsberger (Guest)
on 2010-02-03 18:26
(Received via mailing list)
2010/2/3 Nicolás Sanguinetti <godfoca@gmail.com>:
> responsibility of the models, not the controllers.
>
> And add the helper method on the models.

I strongly agree. Extracting the methods to the controller is merely
an intermediate step. If the methods belong on other objects, then
that tends to become clear soon enough.
--
J. B. (Joe) Rainsberger :: http://www.jbrains.ca ::
http://blog.thecodewhisperer.com
Diaspar Software Services :: http://www.diasparsoftware.com
Author, JUnit Recipes
2005 Gordon Pask Award for contribution to Agile practice :: Agile
2010: Learn. Practice. Explore.
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.