Apologies as this feels like an FAQ, but the only answer I can find refers to a bug in a much earlier version of rspec, and this feels like a common thing to do, so I suspect we''re doing something stupid. The issue seems to be that if we mock a class, that mock carries between specs when running ''rake spec'' - the specs pass when run individually. In one spec, we mock an active record model: MachineInstance = mock("MachineInstance") in a spec in another file that runs later, when we access MachineInstance as what should be a regular activerecord class, it''s still a mock: Spec::Mocks::MockExpectationError in ''MachineInstance should create a new instance given valid attributes'' Mock ''MachineInstance'' received unexpected message :create! with ({:name=>"value for name", :machine_image_id=>1}) We''re running rspec 1.1.12. Thanks for any pointers. Marcus -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20090227/6381c483/attachment.html>
On Fri, Feb 27, 2009 at 8:14 AM, Marcus Roberts <M.Roberts at nittygritty.net> wrote:> Apologies as this feels like an FAQ, but the only answer I can find refers > to a bug in a much earlier version of rspec, and this feels like a common > thing to do, so I suspect we''re doing something stupid. > > The issue seems to be that if we mock a class, that mock carries between > specs when running ''rake spec'' - the specs pass when run individually. > > In one spec, we mock an active record model: > > MachineInstance = mock("MachineInstance") > > in a spec in another file that runs later, when we access MachineInstance as > what should be a regular activerecord class, it''s still a mock: > > Spec::Mocks::MockExpectationError in ''MachineInstance should create a new > instance given valid attributes'' > Mock ''MachineInstance'' received unexpected message :create! with > ({:name=>"value for name", :machine_image_id=>1}) > > We''re running rspec 1.1.12. > > Thanks for any pointers. > > Marcus > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >Unless RSpec is doing some magic under the hood that I''m not aware of, this is expected behavior - you''re reassigning the constant value of MachineInstance. What you really want to do is mock/stub directly on MacineInstance. e.g. MachineInstance.should_receive(:create).and_return @mock_machine Pat
On Fri, Feb 27, 2009 at 10:14 AM, Marcus Roberts <M.Roberts at nittygritty.net> wrote:> Apologies as this feels like an FAQ, but the only answer I can find refers > to a bug in a much earlier version of rspec, and this feels like a common > thing to do, so I suspect we''re doing something stupid. > > The issue seems to be that if we mock a class, that mock carries between > specs when running ''rake spec'' - the specs pass when run individually. > > In one spec, we mock an active record model: > > MachineInstance = mock("MachineInstance")This is not an rspec bug :) This is assigning the value mock("MachineInstance") to the constant MachineInstance, which an interesting way to approach stubbing a class, but not really the idea. Can you show us the spec this is in so we can make appropriate recommendations? Thanks, David> > in a spec in another file that runs later, when we access MachineInstance as > what should be a regular activerecord class, it''s still a mock: > > Spec::Mocks::MockExpectationError in ''MachineInstance should create a new > instance given valid attributes'' > Mock ''MachineInstance'' received unexpected message :create! with > ({:name=>"value for name", :machine_image_id=>1}) > > We''re running rspec 1.1.12. > > Thanks for any pointers. > > Marcus > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
On 27 Feb 2009, at 16:14, Marcus Roberts wrote:> Apologies as this feels like an FAQ, but the only answer I can find > refers to a bug in a much earlier version of rspec, and this feels > like a common thing to do, so I suspect we''re doing something stupid. > > The issue seems to be that if we mock a class, that mock carries > between specs when running ''rake spec'' - the specs pass when run > individually. > > In one spec, we mock an active record model: > > MachineInstance = mock("MachineInstance") >From this code above, you''re setting a constant, ''MachineInstance''. Ruby will keep that constant for subsequent test runs - nothing to do with RSpec. Assuming you want to mock an *instance* of your MachineInstance class, use this in your spec: machine_instance = mock("MachineInstance") AFAIK Ruby uses the case of the first letter to determine whether something''s a (global) constant or a (local) variable. You want the latter, I think, so use lower case letters. If what you want to do is mock the class itself, that''s a different story, and we can talk about that. Matt Wynne http://blog.mattwynne.net http://www.songkick.com
>This is not an rspec bug :)I thought not :-)>This is assigning the value mock("MachineInstance") to the constant >MachineInstance, which an interesting way to approach stubbing a >class, but not really the idea.>Can you show us the spec this is in so we can make appropriate recommendations?We have a class declared in lib/ in a Rails app which we want to test independently of the activerecord class / database. The full spec as it stands is: require File.expand_path(File.dirname(__FILE__) + ''/../spec_helper'') describe "a service at level 0" do it "should pass a test and do nothing" do service = mock("service") service.should_receive(:test_service) service.should_receive(:save) machineInstance = mock("MachineInstance") machineInstance.should_receive(:services).and_return([service]) MachineInstance = mock("MachineInstance") MachineInstance.should_receive(:find).and_return([machineInstance]) monitoringAlerts = MonitoringAlerts.new monitoringAlerts.monitor end end The idea is to have no dependency on the MachineInstance class in this spec, which seems to work, but specs called later still see MachineInstance as the mock. Are we doing this completely the wrong way? Thanks! Marcus -------------- next part -------------- The WatchGuard Firebox that protects your network has detected a message that may not be safe. Cause : The file type may not be safe. Content type : application/ms-tnef File name : winmail.dat Virus status : No information. Action : The Firebox deleted winmail.dat. Your network administrator can not restore this attachment.
>Unless RSpec is doing some magic under the hood that I''m not aware of, >this is expected behavior - you''re reassigning the constant value of >MachineInstance.>What you really want to do is mock/stub directly on MacineInstance. e.g.>MachineInstance.should_receive(:create).and_return @mock_machineIf we did that, would that stub/mock carry over into other spec files run later by autospec, which is what we''re seeing. I can see we''re changing the definition of MachineInstance in the spec we do that in, but should that carry over to later specs? Marcus -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20090227/9a336458/attachment.html>
bloody mailing list lag - three of us answered the same question! On 27 Feb 2009, at 16:46, Matt Wynne wrote:> > On 27 Feb 2009, at 16:14, Marcus Roberts wrote: > >> Apologies as this feels like an FAQ, but the only answer I can find >> refers to a bug in a much earlier version of rspec, and this feels >> like a common thing to do, so I suspect we''re doing something stupid. >> >> The issue seems to be that if we mock a class, that mock carries >> between specs when running ''rake spec'' - the specs pass when run >> individually. >> >> In one spec, we mock an active record model: >> >> MachineInstance = mock("MachineInstance") >> > From this code above, you''re setting a constant, ''MachineInstance''. > Ruby will keep that constant for subsequent test runs - nothing to > do with RSpec. > > Assuming you want to mock an *instance* of your MachineInstance > class, use this in your spec: > > machine_instance = mock("MachineInstance") > > AFAIK Ruby uses the case of the first letter to determine whether > something''s a (global) constant or a (local) variable. You want the > latter, I think, so use lower case letters. > > If what you want to do is mock the class itself, that''s a different > story, and we can talk about that. > > Matt Wynne > http://blog.mattwynne.net > http://www.songkick.com > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-usersMatt Wynne http://blog.mattwynne.net http://www.songkick.com
On 27 Feb 2009, at 17:08, Marcus Roberts wrote:> >Unless RSpec is doing some magic under the hood that I''m not aware > of, > >this is expected behavior - you''re reassigning the constant value of > >MachineInstance. > > >What you really want to do is mock/stub directly on > MacineInstance. e.g. > > >MachineInstance.should_receive(:create).and_return @mock_machine > > If we did that, would that stub/mock carry over into other spec > files run later by autospec, which is what we''re seeing. > I can see we''re changing the definition of MachineInstance in the > spec we do that in, but should that carry over to later specs? >That''s the way Ruby behaves, yes. Once you assign the constant MachineInstance to point to your mock, Rails'' magic auto-file-loading which normally happens when Ruby fires a ''missing constant error'' will never run, so your real class will never get loaded. Assigning constants in your tests is a pretty bad idea unless you know exactly what you''re doing. Matt Wynne http://blog.mattwynne.net http://www.songkick.com
On Fri, Feb 27, 2009 at 9:08 AM, Marcus Roberts <M.Roberts at nittygritty.net> wrote:> > >>Unless RSpec is doing some magic under the hood that I''m not aware of, >>this is expected behavior - you''re reassigning the constant value of >>MachineInstance. > >>What you really want to do is mock/stub directly on MacineInstance.? e.g. > >>MachineInstance.should_receive(:create).and_return @mock_machine > > If we did that, would that stub/mock carry over into other spec files run > later by autospec, which is what we''re seeing.No, because RSpec keeps track of partial mocks (mocks/stubs on "real" objects) and clears them out at the end of the example run.> I can see we''re changing the definition of MachineInstance in the spec we do > that in, but should that carry over to later specs?Yes. This is a Ruby thing, not an RSpec thing. Same as if you were to do MachineInstance = SomeOtherMachineInstance where SomeOtherMachineInstance is a class that you defined. You''re reassigning a constant. Why would RSpec know or care when you do that? Put another way, while RSpec knows when you create a mock object, it has no clue when/if you assigned it to something, and it certainly doesn''t know the previous value. The built-in framework recognizes that your intent when partially mocking an object is to use it within a certain, small scope (an example). It then politely cleans up after itself. If you work within those parameters, you''ll see the behavior that you want. If you _really_ want to just assign the constant (and I don''t see why you would...mocking methods on it directly works fine), you can store the constant''s value in a before and reset it in after: describe "blah" do before(:each) do @klass = MachineInstance MachineInstance = mock(''mi'') end ... after(:each) do MachineInstance = @klass end end Pat
>That''s the way Ruby behaves, yes.>Once you assign the constant MachineInstance to point to your mock, >Rails'' magic auto-file-loading which normally happens when Ruby fires >a ''missing constant error'' will never run, so your real class will >never get loaded.>Assigning constants in your tests is a pretty bad idea unless you know >exactly what you''re doing.Assuming that assigning a constant was the right thing to do, it means there''s different behaviour when running autospec versus running the specs individually. I''m going to assume that we''re doing the wrong thing by assigning a constant, which changes my question to how do we mock/stub the behaviour of our classes without that behaviour passing from spec to spec when running autospec. Thanks Marcus -------------- next part -------------- The WatchGuard Firebox that protects your network has detected a message that may not be safe. Cause : The file type may not be safe. Content type : application/ms-tnef File name : winmail.dat Virus status : No information. Action : The Firebox deleted winmail.dat. Your network administrator can not restore this attachment.
>No, because RSpec keeps track of partial mocks (mocks/stubs on "real" >objects) and clears them out at the end of the example run.Thank you! This is the key, along with understanding we can just mock/stub onto the real object, without needing to make the object a mock object. As soon as we just mock/stub, everything works as expected. Thanks everyone for all the help! Marcus -------------- next part -------------- The WatchGuard Firebox that protects your network has detected a message that may not be safe. Cause : The file type may not be safe. Content type : application/ms-tnef File name : winmail.dat Virus status : No information. Action : The Firebox deleted winmail.dat. Your network administrator can not restore this attachment.
On Fri, Feb 27, 2009 at 9:36 AM, Marcus Roberts <M.Roberts at nittygritty.net> wrote:> I''m going to assume that we''re doing the wrong thing by assigning a constant, which changes my question to how do we mock/stub the behaviour of our classes without that behaviour passing from spec to spec when running autospec.Take out the constant reassignment. Did you see my comments earlier about partial mocking? Pat