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@rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users > _______________________________________________ rspec-users mailing list rspec-users@rubyforge.org http://rubyforge.org/mailman/listinfo/rspec-users