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 #[email protected]}" 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
> [email protected]
> http://rubyforge.org/mailman/listinfo/rspec-users
>
_______________________________________________
rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users