[rspec-users] When to use Factories, Mock Models, Mocks & Stubs

Nicolás Sanguinetti godfoca at gmail.com
Wed Feb 3 11:35:01 EST 2010


On Wed, Feb 3, 2010 at 2:07 PM,  <me at 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

>
> Once again, thanks++
> Frank
>
>
> Quoting David Chelimsky <dchelimsky at gmail.com>:
>
>> On Wed, Feb 3, 2010 at 8:52 AM,  <me at franklakatos.com> wrote:
>>>
>>> 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)
>>
>> 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 = []
>>
>>>          @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?
>>
>> 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
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>> Quoting Adam Sroka <adam.sroka at gmail.com>:
>>>
>>>> On Tue, Feb 2, 2010 at 9:53 PM, Andrei Erdoss <erdoss at gmail.com> wrote:
>>>>>
>>>>> 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.
>>>>>
>>>>
>>>> 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.
>>>> _______________________________________________
>>>> rspec-users mailing list
>>>> rspec-users at rubyforge.org
>>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>>>
>>>
>>>
>>>
>>> _______________________________________________
>>> rspec-users mailing list
>>> rspec-users at rubyforge.org
>>> http://rubyforge.org/mailman/listinfo/rspec-users
>>>
>> _______________________________________________
>> rspec-users mailing list
>> rspec-users at rubyforge.org
>> http://rubyforge.org/mailman/listinfo/rspec-users
>>
>
>
>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>


More information about the rspec-users mailing list