Hi everyone, I have a test for importing vcards, and I was finding that each time it was being run, it was generating new photos and since it was using the test database, it was actually overwriting real photos that were being stored in the file system (they are named based on the record''s id). So it was apparent to me that I need to use mocking to intercept Photo.create so that it doesn''t really happen... I tried to do this, but I am getting this MockExpectationError, which I don''t quite understand the meaning of :[] --- The relationships are: Photo belongs_to :contact Contact has_one :photo --- this is how I am mocking in my test: @photo = mock_model(Photo) Photo.should_receive(:create).and_return(@photo) --- the relevant code that is getting called to create the photo: def card_photo @contact.photo = Photo.create(:image_file_string => @card.photos.first) end --- And... the error: 1) Spec::Mocks::MockExpectationError in ''Vcard importing a vcard a card with an invalid address, a photo, birthday, url, phone, and email should update an existing contact when an email is found'' Mock "Photo_1001" received unexpected message :[]= with ("contact_id", 201) /Library/Ruby/Gems/1.8/gems/activerecord-2.3.5/lib/active_record/associations/association_proxy.rb:185:in `set_belongs_to_association_for'' /Library/Ruby/Gems/1.8/gems/activerecord-2.3.5/lib/active_record/associations/has_one_association.rb:56:in `replace'' /Library/Ruby/Gems/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1281:in `photo='' /Library/Ruby/Gems/1.8/gems/activerecord-2.3.5/lib/active_record/associations/association_proxy.rb:217:in `send'' /Library/Ruby/Gems/1.8/gems/activerecord-2.3.5/lib/active_record/associations/association_proxy.rb:217:in `method_missing'' /Users/patrick/coding/rails/xyz/app/models/vcard.rb:164:in `card_photo'' ---- Any ideas? Thanks.. Patrick J. Collins http://collinatorstudios.com
On 2010-04-25 3:43 AM, Patrick J. Collins wrote:> Hi everyone, > > I have a test for importing vcards, and I was finding that each time it was > being run, it was generating new photos and since it was using the test > database, it was actually overwriting real photos that were being stored in the > file system (they are named based on the record''s id). So it was apparent to > me that I need to use mocking to intercept Photo.create so that it doesn''t > really happen... > >...snip...> Any ideas? > > Thanks.. > > Patrick J. Collins > http://collinatorstudios.comHi Patrick, See http://gist.github.com/378489 for how I ended up specing a file upload. This isn''t the exact same situation, but maybe you will get some ideas that will help. And there may be a better way, but this seems to work and is fairly easy for me understand. Here''s a little info on the app to shed some light on what I''m doing in the spec. I am working on an app that has a "media library" that users can upload into. There are four types of media: audio, video, images, and documents. During the upload process, I try to determine the mime type automatically, and from that, the type of media, but I do allow those things to be set manually. I have a run-time constant (APP_CONFIG[:media_root_path]) that defines where the files should be on the file system, and I have to change that during the test to make sure development files don''t get overwritten. That''s accomplished with the call to set_app_config, which is really nothing more than replacing a constant. Its definition is def set_app_config(new_app_config) saved_app_config = APP_CONFIG.dup Kernel::silence_warnings { Object.const_set(:APP_CONFIG, new_app_config ) } return saved_app_config end Let me know if you have any questions. Peace, Phillip
On Sun, 25 Apr 2010, Phillip Koebbe wrote:> See http://gist.github.com/378489 for how I ended up specing a file upload. > This isn''t the exact same situation, but maybe you will get some ideas that > will help. And there may be a better way, but this seems to work and is fairly > easy for me understand.Yeah, I see what you''re doing, but I don''t quite get how I could apply this to what I am doing. I have a method that creates a contact and then creates a photo and assigns it to that contact. So my problem comes from doing (in my model method): @contact.photo = Photo.create(...) ... Although the code works fine in the real world app use, the spec fails because it''s trying to do something with "[]=" .. which I don''t get. ---- Also, I have a couple side questions-- 1) is there some sort of caching done to before(:all/:each) blocks? I had a before block that set an instance variable to something, and then I removed it-- and was getting weird behavior, and after doing some inspecting I found that the instance variable was still set-- even though it was not set anywhere in the code... I had to put a blank before block in there to get that behavior to stop... Just wondered if this is normal? ... 2) I still am struggling with mock vs stub-- or I should say stub! vs. should_receive. Initially I had something like this: before(:all) do foo = mock_model) Foo.should_receive(:find_by_name).with("bar").and_receive(foo) end it "blah" do 3.times { @blah.do_something } do it "blah2" do 3.times { @blah.do_something } end (blah model) def do_something @foo = Foo.find_by_name("bar") end This wasn''t working, because apparently it''s only good for one usage. I was thinking that declating "should_receive" would mean that anytime Foo gets .find_by_name called on it, it will have that behavior. I ended up changing that to a before(:each), and then I got errors like expected it 1 time but got it 3 times.... So not knowing how to deal with that, I changed .should_receive to .stub! and then the errors went away. Not sure if that was the right thing to do in that case. Patrick J. Collins http://collinatorstudios.com
On 2010-04-25 4:25 PM, Patrick J. Collins wrote:> On Sun, 25 Apr 2010, Phillip Koebbe wrote: >> See http://gist.github.com/378489 for how I ended up specing a file upload. >> This isn''t the exact same situation, but maybe you will get some ideas that >> will help. And there may be a better way, but this seems to work and is fairly >> easy for me understand. > Yeah, I see what you''re doing, but I don''t quite get how I could apply this to > what I am doing. >I was just thinking that since your problem had to do with files on the file system getting overwritten, seeing how to someone worked around that problem with uploads might trigger some ideas in your mind.> I have a method that creates a contact and then creates a photo and assigns it > to that contact. > > So my problem comes from doing (in my model method): > > @contact.photo = Photo.create(...) > > ... Although the code works fine in the real world app use, the spec fails > because it''s trying to do something with "[]=" .. which I don''t get. >I may be mistaken, but []= looks like an array assignment, which would suggest to me something to do with adding an element to a collection somewhere. Do you have any hooks defined that affect the creation process? Or is this a straight-forward, vanilla Photo.create call that goes straight into ActiveRecord? Maybe you should gist your spec and file.> ---- > > Also, I have a couple side questions-- > > 1) is there some sort of caching done to before(:all/:each) blocks? > > I had a before block that set an instance variable to something, and then I > removed it-- and was getting weird behavior, and after doing some inspecting I > found that the instance variable was still set-- even though it was not set > anywhere in the code... I had to put a blank before block in there to get that > behavior to stop... Just wondered if this is normal? >I''ve not experienced that problem, so I can''t help with this.> 2) I still am struggling with mock vs stub-- or I should say stub! vs. > should_receive. Initially I had something like this: > > before(:all) do > foo = mock_model) > Foo.should_receive(:find_by_name).with("bar").and_receive(foo) > end > > it "blah" do > 3.times { @blah.do_something } > do > > it "blah2" do > 3.times { @blah.do_something } > end > > (blah model) > def do_something > @foo = Foo.find_by_name("bar") > end > > This wasn''t working, because apparently it''s only good for one usage. I was > thinking that declating "should_receive" would mean that anytime Foo gets > .find_by_name called on it, it will have that behavior. I ended up changing > that to a before(:each), and then I got errors like expected it 1 time but got > it 3 times.... > > So not knowing how to deal with that, I changed .should_receive to .stub! and > then the errors went away. > > Not sure if that was the right thing to do in that case. >Use stub when you just want to provide the plumbing. Use should_receive when you are setting an expectation. As an example, you could do Photo.stub(:create) just to provide the method to avoid errors when your code calls it. But if you actually want to verify that your code calls it, then do Photo.should_receive(:create). After David gave his input a few weeks ago, I''ve adopted an approach in which I stub methods in before(:each), including return values where appropriate, and then put an expectation in an example. So I might have something like: before(:each) do widget = stub_model(Widget).as_new_record Widget.stub(:new).and_return(widget) end it ''should call Widget.new'' do Widget.should_receive(:new) get :new end it ''should render the new template'' do get :new response.should render_template(:new) end In this example, the real code is going to call .new both times, but I''m setting an expectation just once (trying to follow the convention of one test per example). In the first example, where the expectation is set, I''ll get a failure if .new isn''t called. In the second one, no expectation is set, but since I''m using a stubbed model, I don''t want the class actually calling real methods, hence the stub. I personally find it''s easier and more logical to do it this way. Doing it like this, I could remove the first example and nothing else breaks and it still makes perfectly good sense. Peace, Phillip
Here is a gist of my code: http://gist.github.com/378778> Use stub when you just want to provide the plumbing. Use should_receive when > you are setting an expectation. As an example, you could do > Photo.stub(:create) just to provide the method to avoid errors when your code > calls it. But if you actually want to verify that your code calls it, then doWell, as you can see by my code, I am actually doing a loop @card.addresses.each .... This is calling the @country = get_country || Country.find_by_name("United States") So-- the number of times .find_by_name needs to be intercepted by .should_receive is dependent on the number of addresses in a vcard. This was the problem I didn''t know how to address, and using stub! resolved it, but like I said-- I didn''t know if there''s a more appropriate way to do it. Patrick J. Collins http://collinatorstudios.com
On 2010-04-25 5:25 PM, Patrick J. Collins wrote:> Here is a gist of my code: > http://gist.github.com/378778 > >> Use stub when you just want to provide the plumbing. Use should_receive when >> you are setting an expectation. As an example, you could do >> Photo.stub(:create) just to provide the method to avoid errors when your code >> calls it. But if you actually want to verify that your code calls it, then do > Well, as you can see by my code, I am actually doing a loop > > @card.addresses.each .... This is calling the @country = get_country || > Country.find_by_name("United States") > > So-- the number of times .find_by_name needs to be intercepted by > .should_receive is dependent on the number of addresses in a vcard.And this would be the difference between stubbing and expecting. Do you need to set an expectation that it gets called a certain number of times? If not, just stub it (which is what you did and the error went away). Only use should_receive when you want to verify that a method is actually being called in your code. I generally do that when I want to make sure different branches of execution are followed correctly.> This was the problem I didn''t know how to address, and using stub! resolved it, > but like I said-- I didn''t know if there''s a more appropriate way to do it. > > Patrick J. Collins > http://collinatorstudios.comRegarding your original problem of the mocking error, have you debugged into the FlexImage code? In looking at your gist, you have this: # @photo = mock_model(Photo, {:image_file_string => photo_content}) which says "create a mock object based on the Photo model, and if #image_file_string gets called on that object, return photo_content". Then you have # Photo.should_receive(:create).and_return(@photo) which obviously says "Photo should receive a call to the .create method, and when it does, return @photo". The potential problem that I see (though I may not be understanding something correctly), is when you call Photo.create, .image_file_string= is being called in the FlexImage codebase. I''m wondering if your issue might be somewhere down in there. I''d do some more digging, but I have to run right now. Good luck! Peace, Phillip> _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
> Regarding your original problem of the mocking error, have you debugged into > the FlexImage code? In looking at your gist, you have this: > > # @photo = mock_model(Photo, {:image_file_string => photo_content}) > > which says "create a mock object based on the Photo model, and if > #image_file_string gets called on that object, return photo_content". Then you > have > > # Photo.should_receive(:create).and_return(@photo)... Well, from what I can tell, the problem is not with Photo.should_receive... If I change @contact.photo = Photo.create(...) to Photo.create(...) then the spec passes... It''s when that @contact.photo = is there that it breaks. Patrick J. Collins http://collinatorstudios.com