James Moore
2007-Mar-18 17:03 UTC
[mocha-developer] Automatically mocking ActiveRecord methods
Seems like I do a fair amount of mocking ActiveRecord''s automatically generated routines when I''m testing controllers. Things like: @portalspace = PortalSpace.new :public_readable => true, :public_writable => true, :name => ''public'' @portalspace.stubs(:id).returns(203) PortalSpace.stubs(:find).with(203).returns(@portalspace) PortalSpace.stubs(:find).with(''203'').returns(@portalspace) PortalSpace.stubs(:find).with(:first, {:conditions => [''portal_spaces.name = ?'', ''public'']}).returns(@portalspace) Is there value in having an addon that says something like: foo.stub_active_record that would add some set of mocks/stubs for things like foo.stub(:find).with(foo.id).return(foo) foo.stub(:find).with(foo.id.to_s).return(foo) foo.stub(:find_by_name).with(foo.name).return(foo) etc. - James Moore
James Mead
2007-Mar-27 14:01 UTC
[mocha-developer] Automatically mocking ActiveRecord methods
I can see why you might want to do this. I''m not sure I would want to add another public instance method to Object - recently I''ve been looking into how to reduce the number of such methods added to Object. Also I don''t really want to incorporate any more Rails-specific code into Mocha. The Mocha Rails plugin is currently little more than a conditional require statement. Can I suggest you consider creating your own Rails plugin and see how things go...? -- James. http://blog.floehopper.org
James Moore
2007-Apr-02 16:24 UTC
[mocha-developer] Automatically mocking ActiveRecord methods
After I wrote this, I started to have second thoughts. It seemed like this was mostly useful in Rails controller tests as a way of abstracting away the database. But then I started running into too many situations where I had handcrafted SQL in use, and mocking that clearly was the wrong way to go. (We''re storing something that looks like a directory tree in SQL records, and the live installation has a few million entries. The standard rails things like acts_as_tree are hopeless for performance without handcrafting some queries.) I''m now wondering whether a better way to do this is to add a facility to add callbacks that can create mocks and stubs when a particular object is instantiated from the database. A sort of cleanup method on Rails find*() methods that will allow you to do mock/stub methods on an object with a particular id (or some set of criteria) when it''s created. In any case, I agree that it should be some sort of addon facility, and I don''t think there''s any reason why it would need to touch Object directly. - James Moore On 3/27/07, James Mead <jamesmead44 at gmail.com> wrote:> > I can see why you might want to do this. I''m not sure I would want to add > another public instance method to Object - recently I''ve been looking into > how to reduce the number of such methods added to Object. Also I don''t > really want to incorporate any more Rails-specific code into Mocha. The > Mocha Rails plugin is currently little more than a conditional require > statement. > > Can I suggest you consider creating your own Rails plugin and see how > things > go...? > > -- > James. > http://blog.floehopper.org > _______________________________________________ > mocha-developer mailing list > mocha-developer at rubyforge.org > http://rubyforge.org/mailman/listinfo/mocha-developer >
Frederick Cheung
2007-Apr-02 16:29 UTC
[mocha-developer] Automatically mocking ActiveRecord methods
On 2 Apr 2007, at 17:24, James Moore wrote:> > I''m now wondering whether a better way to do this is to add a > facility to > add callbacks that can create mocks and stubs when a particular > object is > instantiated from the database. A sort of cleanup method on Rails > find*() > methods that will allow you to do mock/stub methods on an object > with a > particular id (or some set of criteria) when it''s created.I did this via the after_find callback a few weeks ago. I posted my code here when I did it maybe you''ll find some inspiration there ? Fred> > In any case, I agree that it should be some sort of addon facility, > and I > don''t think there''s any reason why it would need to touch Object > directly. > > - James Moore > > On 3/27/07, James Mead <jamesmead44 at gmail.com> wrote: >> >> I can see why you might want to do this. I''m not sure I would want >> to add >> another public instance method to Object - recently I''ve been >> looking into >> how to reduce the number of such methods added to Object. Also I >> don''t >> really want to incorporate any more Rails-specific code into >> Mocha. The >> Mocha Rails plugin is currently little more than a conditional >> require >> statement. >> >> Can I suggest you consider creating your own Rails plugin and see how >> things >> go...? >> >> -- >> James. >> http://blog.floehopper.org >> _______________________________________________ >> mocha-developer mailing list >> mocha-developer at rubyforge.org >> http://rubyforge.org/mailman/listinfo/mocha-developer >> > _______________________________________________ > mocha-developer mailing list > mocha-developer at rubyforge.org > http://rubyforge.org/mailman/listinfo/mocha-developer
James Moore
2007-Apr-18 18:33 UTC
[mocha-developer] Automatically mocking ActiveRecord methods
On 4/2/07, Frederick Cheung <fred at 82ask.com> wrote:> > > On 2 Apr 2007, at 17:24, James Moore wrote: > > > > > I''m now wondering whether a better way to do this is to add a > > facility to > > add callbacks that can create mocks and stubs when a particular > > object is > > instantiated from the database. A sort of cleanup method on Rails > > find*() > > methods that will allow you to do mock/stub methods on an object > > with a > > particular id (or some set of criteria) when it''s created. > > I did this via the after_find callback a few weeks ago. I posted my > code here when I did it maybe you''ll find some inspiration there ? > > Fred >Thanks Fred - I took a look, and here''s what I''m currently playing with. First, I tried doing just a mock of the WhateverRecord.expects(:find).with(32).returns(my_mocked_obj). The problem there was that Rails doesn''t always just do a find with an id; when you''ve got relations and validations, you''ll get finds that specify more than just the id itself. What does get called every time, with the things you want arranged in a nice convenient package, is #instantiate. It''s the method that turns the data for the row into an object, and it''s primary purpose in life is to support STI. So I hijacked it, and now am using something that looks like this: Annotation.send :include, MockActiveRecord foo = Foo.new :whatever => ''blah'' foo.expects(:snark).with(999) Foo.mock_active_record(foo.id => foo) Every time a record of class Foo is created, the id is checked to see if it matches an id passed to mock_active_record(). If there''s a match, that object is returned, otherwise the normal #instantiate is called to build a new object. Here''s the current (experimental) code: # Adds mock_active_record to an ActiveRecord class. # # Any time an ActiveRecord object is instantiated, # its id will be checked against a list of mocked # ids. If the new object is present in that list, # use the mock instead of the object produced # by ActiveRecord. # # Example: # # class FooTest < Test::Unit::TestCase # def test_foo # Foo.send :include, MockActiveRecord # # # Create a new Foo in the database # f = Foo.create :msg => ''blah'' # # # Add a stub that will be used # # in place of the Foo that was just # # created. # Foo.mock_active_record(f.id => stub(:msg => :abc)) # # # find() will match the stub instead # # of the record from the database. # # f = Foo.find(f.id) # assert_equal :abc, f.msg # end # end module InterceptMethod def self.included target target.extend ClassMethods end module ClassMethods def intercept_method call_name, &block unbound_method = instance_method call_name define_method call_name do |*args| bound_method = unbound_method.bind(self) block.call(bound_method, *args) end end def intercept_class_method call_name, &block class_method = method call_name s = class << self; self; end s.send :define_method, call_name do |*args| block.call(class_method, *args) end end end end module MockActiveRecord # Classes that include MockActiveRecord # get: # # - an overridden instantiate() # - mock_active_record() def self.included target target.class_eval do include InterceptMethod extend ClassMethods intercept_class_method :instantiate do |method_obj, *args| mocked_record_match?(*args) || method_obj.call(*args) end end end module ClassMethods def mock_active_record ids_to_mocks ids_to_mocks.each_pair do |k, v| mocked_record_store k, v end end protected def mocked_record_match? record @@mocked_records ||= {} return @@mocked_records[record[''id''].to_i] end def mocked_record_store k, v @@mocked_records ||= {} @@mocked_records[k.to_i] = v end end end