Welcome to RSpec, Patrick. For some of us, it’s pretty rocky at first. I
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!
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”
- 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 D.’)
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 D.’, :age => 99, :gender =>
‘M’)
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