[rspec-users] lots of nil problems!

Patrick J. Collins patrick at collinatorstudios.com
Sat Mar 20 08:02:07 EDT 2010


On Sat, 20 Mar 2010, Phillip Koebbe wrote:

> Welcome to RSpec, Patrick. For some of us, it's pretty rocky at first. I

Thank you Phillip for your great explanation...

After reading what you wrote, I have a few questions:

1.  From what I understand of what you wrote, stub_model makes a fake object,
with accessor methods for the AR columns, and they return nil.

So what happens behind the scenes with mock_model?  What is the difference?



2.  I sort of asked this earlier, but didn't really get addressed---  What is the benefit to doing stubbing
vs. using a factory or fixture?



3.  In your code, you wrote:

> context 'when record is not found' do
>   SomeClass.should_receive(:find).and_return(nil)

I just wondered about this, because-- correct me if I am wrong, but a record not found would not return
nil-- it returns a record not found error, correct?  Or does an error actually equate to nil?  I know in a
lot of my controllers to avoid record not found errors, I do .find_by_id instead of .find, because that
will return nil if it's not found.



4.  should_receive...  So, if you have a method that is calling other class and instance methods, isn't it
a given that those calls are important and that they should be expected?  In other words, I am finding
myself asking the same question again-- Why use stub instead of should_receive?  When would you actually
not care whether a method gets called or not if you are testing something.  I mean, I would assume if I am
testing something, I want to test everything that happens-- so any real code in my app that deals with
class/instance methods, those seem like they would be important and therefore those calls should be
expected.  ?



5.  Since I learn really well from example, I just wrote up some random and strange code, which does a
handful of things, and I would love it if you could go through and spec it-- and explain each step of the
way, what you're doing and why.  What is necessary to test, and what is not, and of course also-- why. 

def index
   type = params[:foo]
	value = params[:bar]

	@foo = type && value ? Foo.send("find_by_#{type}", value) : Foo.all

	@baz = /.*lower/.match(params[:baz]) ? @foo.lowered_name : @foo.upper_name

	flash[:notice] = "Something with #{@foo.numbers}" unless params[:bool].to_i.zero?
	
	respond_to do |format|
		format.html { render 'foo_listing' }
		format.js { render :json => { :foo => @foo }.to_json
		format.pdf { render :inline => @foo.pdf }
	end
end

#
class Foo < AR::base

def lowered_name
  name.downcase
end

def upper_name
  name.upcase
end

def numbers
  rand(id)
end

def pdf
  # return some prawn pdf object
end

...

If you could do that for me, it would be incredibly awesome...

Thank you.

Patrick J. Collins
http://collinatorstudios.com


> started using it a couple of years ago with models, and understood that well
> enough (I think). Then I came to controllers and I just couldn't wrap my mind
> around it. I gave up for quite some time. When I came back to testing, it was
> on a project that was using test::unit, but at least I was working with
> someone who had more experience than I did, and I was able to gain some
> understanding that I lacked earlier. But during the use of test::unit, I
> realized I missed RSpec, so when I moved on, I put RSpec back into my
> workflow. I've been trudging along for a few months now, and while I still
> have lots and lots to learn, I think I am actually using it somewhat correctly
> and productively. So while I definitely don't want to try to supersede Nick's
> or anyone else's contributions to your questions, I thought I would chime with
> what I've learned.
> 
> On 2010-03-20 2:28 AM, Patrick J. Collins wrote:
> > Hi Nick,
> > 
> > Thank you very much for your reply.  One thing I am finding incredibly
> > frustrating with Rspec, is that I don't even know what questions to ask--
> >    
> 
> Ask everything. In my life, I've learned that the only stupid question is the
> one you didn't ask. Most of the people in the RSpec community are *very*
> understanding, patient, and incredibly helpful. No one is going to bite your
> head off!
> 
> > because I find the whole thing so confusing.  So forgive me, but I am going
> > to
> > break down your code and ask specific questions to hopefully gain
> > understanding.
> > 
> > 1.  Ok-- so in your example (which I greatly appreciate), you have this
> > do_action
> > method..  Is that where I would put something like
> > 
> > get :create, :format =>  :pdf
> > 
> > ?
> > 
> > ...
> > 
> > 2. Next in the before each block, you set @address..
> > 
> > Now, I have factory girl installed and have used it in cucumber, is that
> > something that could be used instead of setting @address to "123 some st" ?
> > Is
> > that common practice and acceptable to do?
> > 
> > 3.  You set shipping_setting to mock the model Setting with
> > :address set to @address....
> > 
> > > From my understanding of what I've read--  Mocking a model,
> > simply means that it's a fake object, that acts like the real
> > thing------  So, when you do that mock_model, is it virtually
> > the same to have just done:
> > @shipping_setting = Setting.create(:address =>  @address)
> > 
> > ?  That would be utilizing the test database, correct?  By
> > using mock_model, nothing is being stored, right?  It's more
> > just temporary data in memory... ?
> > 
> >    
> 
> No, you're not using the test database. That's the point of mocking: you want
> to break the dependency on another layer. When you test, you want to try to
> isolate what you are testing so you can specify how it behaves given certain
> criteria. For example, when you test a model, you don't want a controller
> involved. When you test a controller, you don't really want a model involved.
> Mocking and stubbing allow you to completely control all (or most) of the
> extenuating circumstances that your controller will find itself in. Then you
> can properly test something like "When the controller receives these params,
> it should do blah blah blah"
> 
> > 4.  You do .stub! ...........  This is where I get really
> > confused.  From the peepcode screencast that I have watched
> > several times, he explained that stubbing is the same as
> > mocking-- except there is no expectation of what it returns.
> > 
> >    
> 
> Stubbing is creating the "plumbing" that your application expects to exist to
> function properly. If you have a person object and you want to do something
> with the name attribute, you code will be expecting "person.name" to work. If
> you have a fake object @person and call @person.name, you will get an error
> unless you stub it like
> 
> @person.stub!(:name).and_return('John Doe')
> 
> > That made sense when he said it, but in all the examples I've
> > seen with code (including yours), there is this .and_return at
> > the end..  Which as far as I can tell, is an expectation..  I
> > mean, you are asking it to return a specific thing-- that is
> > an expectation is it not?  Or does expectation really mean
> > something else in this context?
> > 
> >    
> 
> The .and_return() bit just says "for this stubbed method, return this value
> when it's called." Keep in mind that these objects you are creating are going
> to be interacting with your application code. So if your application code
> calls Person.new, it expects to receive back a person object. So you can do
> something like this in your specs:
> 
> @person = stub_model(Person)
> Person.stub!(:new).and_return(@person)
> 
> Then you will be bypassing your Person model completely but still sending
> something back for your code to play with. Now, I've introduced another method
> in this mess, stub_model. This is very similar to stub, except that it goes
> ahead and and stubs out the attribute methods that are on your model. So if
> you have Person with name, age, and gender, the single call to stub_model also
> does this for you:
> 
> @person.stub!(:name)
> @person.stub!(:age)
> @person.stub!(:gender)
> 
> By default, the stubs created with stub_model will return nil. If you need to
> specify return values, do something like this:
> 
> @person = stub_model(Person, :name => 'John Doe', :age => 99, :gender => 'M')
> 
> > That aside, if I try to guess what this Setting.stub! this is
> > doing-- it is pretending that you are actually looking for a
> > record, and getting it.....  but in this case, is that even
> > worth doing?  I mean, the shipping_setting record is really
> > only important in the sense that the record is needed to
> > provide a valid "from" address for a label..  But, if we're
> > dealing with a test database where a from address doesn't
> > exist-- then it seems kind of pointless..  To clarify, the
> > importance for me would be to have a test that actually looks
> > in my real database and makes sure there is a shipping from
> > address in there...  I have no idea if this paragraph will
> > make sense to you, but hopefully it will.
> > 
> >    
> Here is where testing in isolation comes back into play. If you are testing a
> controller, you want to try to avoid the dependence on the database. Ideally,
> you want to use mocking and stubbing to create an artificial environment, that
> you completely control, to specify the behavior of your controller. Think
> about each situation you expect your controller to encounter. For each one of
> those situations, create the imaginary world that needs to exist, and then
> test that your controller behaves correctly.
> 
> For example, if your controller needs to do one thing if a particular record
> is found and something else if it is not found, you should have two sets of
> specifications. I usually do something like this:
> 
> context 'when records is found' do
>   before :each do
>     @record = stub_model(SomeClass)
>     SomeClass.should_receive(:find).and_return(@record)
>   end
> 
>   it 'should test something' do
>     blah blah blah
>   end
> end
> 
> context 'when record is not found' do
>   SomeClass.should_receive(:find).and_return(nil)
> 
>   it 'should test something else' do
> 
>   end
> end
> 
> I've thrown in another method, .should_receive. This establishes an
> expectation that this method should be called during the execution of your
> code. If it does not get called, you will get a failure. Stub!, on the other
> hand, just creates the method *in case* it gets called. No error will be
> raised if it doesn't. So in the bogus code above, I am saying that I expect my
> controller to call SomeClass.find, and I want to test what it does when the
> find is successful and when it isn't. When it is successful, it will be
> returning something that the application will think is an instance of the
> class, and when it is not successful, it will be returning nil, which is what
> normally happens in AR. However, because I'm "mocking and stubbing", I'm
> bypassing AR completely.
> 
> That's all I have time for right now. I hope this has helped a little bit. I
> encourage you to take deep breaths often and just hang in there. Keep asking
> questions. Soon or later, the light will click and you'll have an "Aha!"
> moment.
> 
> Peace,
> Phillip
> 
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
> 


More information about the rspec-users mailing list