Hi! I need to write some wrapper classes, that derive from SWIG generated proxy classes for some C/C++ extensions. The Ruby wrapper classes need to do a lot of ''super'' calls to the base classes generated by SWIG. The question is: Is there any way to make RSpec mock a base class and record/verify that certain methods on a base class are called? As long as the methods in the derived class have different names this is no problem, but otherwise it doesn''t seem to be possible - at least not with RSpec''s mocking/stubbing facilities, does it? I see other ways to do this, like wrapping an instance of the Swig class instead of deriving from it or writing some custom mocking code that fakes the base class e.g. with define_method, but I''m especially asking if this is possible with RSpec. thx, Tobias module Swig class Base def initialize(arg) end def do_something(arg) end end end module Wrapper class Derived << Swig::Base def initialize(arg) super(arg) yield(self) if block_given? end def do_something(arg) do_something(arg) # additional behaviour here end end end describe Wrapper::Derived do it ''should initialize the base class with the same argument'' do # No idea, how to mock Base.initialize() here end it ''should do something with the base class when doing something'' do # No idea, how to mock Base.do_something() here end end
On Sat, Feb 21, 2009 at 6:31 AM, Tobi <listaccount at e-tobi.net> wrote:> Hi! > > I need to write some wrapper classes, that derive from SWIG generated > proxy classes for some C/C++ extensions. > > The Ruby wrapper classes need to do a lot of ''super'' calls to the base > classes generated by SWIG. > > The question is: Is there any way to make RSpec mock a base class and > record/verify that certain methods on a base class are called? > > As long as the methods in the derived class have different names this is > no problem, but otherwise it doesn''t seem to be possible - at least not > with RSpec''s mocking/stubbing facilities, does it? > > I see other ways to do this, like wrapping an instance of the Swig class > instead of deriving from it or writing some custom mocking code that fakes > the base class e.g. with define_method, but I''m especially asking if this > is possible with RSpec.+1 to composition over inheritance here. Mocking at different layers of an inheritance hierarchy sounds like trouble and screams to pull that thing apart. Composition will allow you to separate an inheritance hierarchy (if you do need one) from the swig base classes, and it provides a clean separation between the swig stuff and your application stuff.> > thx, > > Tobias > > module Swig > class Base > def initialize(arg) > end > > def do_something(arg) > end > end > end > > module Wrapper > class Derived << Swig::Base > def initialize(arg) > super(arg) > yield(self) if block_given? > end > > def do_something(arg) > do_something(arg) > # additional behaviour here > end > end > end > > describe Wrapper::Derived do > it ''should initialize the base class with the same argument'' do > # No idea, how to mock Base.initialize() here > end > > it ''should do something with the base class when doing something'' do > # No idea, how to mock Base.do_something() here > end > end > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
Zach Dennis wrote:> +1 to composition over inheritance here. Mocking at different layers > of an inheritance hierarchy sounds like trouble and screams to pull > that thing apart.Good point! I''ve already tried the composition approach. It solves the testabilitiy problems, but it doesn''t feel right in this case. Inheritance seems to be the more natural approach. The C++ application will call virtual methods on the base class which should be overriden in a derived class. So it seems to make sense to have the same inheritance tree in the Ruby counter parts. If I would use composition, I would at least need to dynamically extend the referenced base class so I can override some of the methods that are the counter parts of the virtual C++ methods. And I would need to expose the referenced class to the C++ code. I''ve finally found a way to kinda mock the base class in some way: module BaseClassMock attr_accessor :base def self.included(klass) class << klass @@base_class = Object.new def base_class return @@base_class end end end def initialize(*args) @@base_class.new(*args) if @@base_class.respond_to?(:new) @base = Object.new end def method_missing(symbol, *args) @base.send(symbol, *args) end end Any base class (Swig classes in my case) should then be declared for RSpec like: module Swig class Base; include BaseClassMock; end end And in the specs I can then do: Derived.base_class.should_receive(:new).with(''something'') d = Derived.new(''something'') or: d = Derived.new d.base.should_receive(:do_something) d.do_something Tobias
Hi all, I''m having the same problem as Tobi and I wanted to try Tobi mock solution but unfortunately it does not work for me. Here is a trivial example I want to test: module ActiveRecord class Base def self.count_by_params(params = {}, options = {}) scoped_by_params(params).count end end end And my spec: module ActiveRecord class Base include BaseClassMock end end class DummyUser < ActiveRecord::Base end it "should pass all the params and options to #scope_by_params" do params = { :foo => 1, :bar => 2 } options = { :foo => 3, :bar => 4 } @dummy = DummyUser @dummy.base_class.should_receive(:scope_by_params).with(params, options) DummyUser.count_by_params(params, options) end With the above example and Tobi''s BaseClassMock module I''m getting "undefined method `should_receive'' for #<Object:0x188c164>". Btw, I also get this when I do not use Tobi be it for for #<Class:0x188cdf8> instead of Object. Any suggestions on what I''m doing wrong or a different approach? Cheers, Jeroen On Sat, Feb 21, 2009 at 10:24 PM, Tobi <listaccount at e-tobi.net> wrote:> Zach Dennis wrote: > > > +1 to composition over inheritance here. Mocking at different layers > > of an inheritance hierarchy sounds like trouble and screams to pull > > that thing apart. > > Good point! I''ve already tried the composition approach. It solves the > testabilitiy problems, but it doesn''t feel right in this case. > > Inheritance seems to be the more natural approach. The C++ application > will call virtual methods on the base class which should be overriden in a > derived class. So it seems to make sense to have the same inheritance tree > in the Ruby counter parts. > > If I would use composition, I would at least need to dynamically extend > the referenced base class so I can override some of the methods that are > the counter parts of the virtual C++ methods. And I would need to expose > the referenced class to the C++ code. > > I''ve finally found a way to kinda mock the base class in some way: > > module BaseClassMock > attr_accessor :base > > def self.included(klass) > class << klass > @@base_class = Object.new > > def base_class > return @@base_class > end > end > end > > def initialize(*args) > @@base_class.new(*args) if @@base_class.respond_to?(:new) > @base = Object.new > end > > def method_missing(symbol, *args) > @base.send(symbol, *args) > end > end > > Any base class (Swig classes in my case) should then be declared for RSpec > like: > > module Swig > class Base; include BaseClassMock; end > end > > And in the specs I can then do: > > Derived.base_class.should_receive(:new).with(''something'') > d = Derived.new(''something'') > > or: > > d = Derived.new > d.base.should_receive(:do_something) > d.do_something > > Tobias > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20090319/b5cb74c3/attachment.html>
Huray, got it solved! Hopefully you''ll agree with this solution (thanks to mocha sorry) describe "count_by_params" do it "should pass all the params and options to #scope_by_params" do params = { :foo => 1, :bar => 2 } options = { :foo => 3, :bar => 4 } @base = ActiveRecord::Base @result = mock("query results", :count => 1) @base.expects(:scoped_by_params).with(params, options).returns(@result) @base.count_by_params(params, options) end On Thu, Mar 19, 2009 at 5:35 PM, Jeroen van Dijk <jeroentjevandijk at gmail.com> wrote:> Hi all, > > I''m having the same problem as Tobi and I wanted to try Tobi mock solution > but unfortunately it does not work for me. > > Here is a trivial example I want to test: > > module ActiveRecord > class Base > def self.count_by_params(params = {}, options = {}) > scoped_by_params(params).count > end > end > end > > And my spec: > module ActiveRecord > class Base > include BaseClassMock > end > end > > class DummyUser < ActiveRecord::Base > end > > it "should pass all the params and options to #scope_by_params" do > params = { :foo => 1, :bar => 2 } > options = { :foo => 3, :bar => 4 } > @dummy = DummyUser > @dummy.base_class.should_receive(:scope_by_params).with(params, > options) > DummyUser.count_by_params(params, options) > end > > With the above example and Tobi''s BaseClassMock module I''m getting > "undefined method `should_receive'' for #<Object:0x188c164>". Btw, I also get > this when I do not use Tobi be it for for #<Class:0x188cdf8> instead of > Object. > > Any suggestions on what I''m doing wrong or a different approach? > > Cheers, > Jeroen > > > > > On Sat, Feb 21, 2009 at 10:24 PM, Tobi <listaccount at e-tobi.net> wrote: > >> Zach Dennis wrote: >> >> > +1 to composition over inheritance here. Mocking at different layers >> > of an inheritance hierarchy sounds like trouble and screams to pull >> > that thing apart. >> >> Good point! I''ve already tried the composition approach. It solves the >> testabilitiy problems, but it doesn''t feel right in this case. >> >> Inheritance seems to be the more natural approach. The C++ application >> will call virtual methods on the base class which should be overriden in a >> derived class. So it seems to make sense to have the same inheritance tree >> in the Ruby counter parts. >> >> If I would use composition, I would at least need to dynamically extend >> the referenced base class so I can override some of the methods that are >> the counter parts of the virtual C++ methods. And I would need to expose >> the referenced class to the C++ code. >> >> I''ve finally found a way to kinda mock the base class in some way: >> >> module BaseClassMock >> attr_accessor :base >> >> def self.included(klass) >> class << klass >> @@base_class = Object.new >> >> def base_class >> return @@base_class >> end >> end >> end >> >> def initialize(*args) >> @@base_class.new(*args) if @@base_class.respond_to?(:new) >> @base = Object.new >> end >> >> def method_missing(symbol, *args) >> @base.send(symbol, *args) >> end >> end >> >> Any base class (Swig classes in my case) should then be declared for RSpec >> like: >> >> module Swig >> class Base; include BaseClassMock; end >> end >> >> And in the specs I can then do: >> >> Derived.base_class.should_receive(:new).with(''something'') >> d = Derived.new(''something'') >> >> or: >> >> d = Derived.new >> d.base.should_receive(:do_something) >> d.do_something >> >> Tobias >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >> > >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20090320/be5567e4/attachment-0001.html>
2009/3/20 Jeroen van Dijk <jeroentjevandijk at gmail.com>:> Huray, got it solved! Hopefully you''ll agree with this solution (thanks to > mocha sorry) > > > ? describe "count_by_params" do > ??? it "should pass all the params and options to #scope_by_params" do > ????? params? = { :foo => 1, :bar => 2 } > ????? options = { :foo => 3, :bar => 4 } > > ????? @base = ActiveRecord::Base > ????? @result = mock("query results", :count => 1) > ????? @base.expects(:scoped_by_params).with(params, > options).returns(@result) > ????? @base.count_by_params(params, options) > ??? end >Is #scoped_by_params a public method that has its own examples? Down at the model level I would want to know that count_by_params actually was working as expected and not just collaborating with itself to call an internal method. I don''t know how crazy scoped_by_params is so maybe there is a potential risk you''re willing to accept given the complexity of what it does, but this example raises my eyebrow as to the behaviour your actually expecting and verifying.> > On Thu, Mar 19, 2009 at 5:35 PM, Jeroen van Dijk > <jeroentjevandijk at gmail.com> wrote: >> >> Hi all, >> >> I''m having the same problem as Tobi and I wanted to try Tobi mock solution >> but unfortunately it does not work for me. >> >> Here is a trivial example I want to test: >> >> module ActiveRecord >> ? class Base >> ????? def self.count_by_params(params = {}, options = {}) >> ??? ????? scoped_by_params(params).count >> ??? ? end >> ? end >> end >> >> And my spec: >> module ActiveRecord >> ? class Base >> ??? include BaseClassMock >> ? end >> end >> >> class DummyUser < ActiveRecord::Base >> end >> >> it "should pass all the params and options to #scope_by_params" do >> ?? params? = { :foo => 1, :bar => 2 } >> ??? options = { :foo => 3, :bar => 4 } >> ??? @dummy = DummyUser >> ??? @dummy.base_class.should_receive(:scope_by_params).with(params, >> options) >> ??? DummyUser.count_by_params(params, options) >> end >> >> With the above example and Tobi''s BaseClassMock module I''m getting >> "undefined method `should_receive'' for #<Object:0x188c164>". Btw, I also get >> this when I do not use Tobi be it for for #<Class:0x188cdf8> instead of >> Object. >> >> Any suggestions on what I''m doing wrong or a different approach? >> >> Cheers, >> Jeroen >> >> >> >> On Sat, Feb 21, 2009 at 10:24 PM, Tobi <listaccount at e-tobi.net> wrote: >>> >>> Zach Dennis wrote: >>> >>> > +1 to composition over inheritance here. Mocking at different layers >>> > of an inheritance hierarchy sounds like trouble and screams to pull >>> > that thing apart. >>> >>> Good point! I''ve already tried the composition approach. It solves the >>> testabilitiy problems, but it doesn''t feel right in this case. >>> >>> Inheritance seems to be the more natural approach. The C++ application >>> will call virtual methods on the base class which should be overriden in >>> a >>> derived class. So it seems to make sense to have the same inheritance >>> tree >>> in the Ruby counter parts. >>> >>> If I would use composition, I would at least need to dynamically extend >>> the referenced base class so I can override some of the methods that are >>> the counter parts of the virtual C++ methods. And I would need to expose >>> the referenced class to the C++ code. >>> >>> I''ve finally found a way to kinda mock the base class in some way: >>> >>> module BaseClassMock >>> ?attr_accessor :base >>> >>> ?def self.included(klass) >>> ? ?class << klass >>> ? ? ?@@base_class = Object.new >>> >>> ? ? ?def base_class >>> ? ? ? ?return @@base_class >>> ? ? ?end >>> ? ?end >>> ?end >>> >>> ?def initialize(*args) >>> ? ?@@base_class.new(*args) if @@base_class.respond_to?(:new) >>> ? ?@base = Object.new >>> ?end >>> >>> ?def method_missing(symbol, *args) >>> ? ?@base.send(symbol, *args) >>> ?end >>> end >>> >>> Any base class (Swig classes in my case) should then be declared for >>> RSpec >>> like: >>> >>> module Swig >>> ?class Base; include BaseClassMock; end >>> end >>> >>> And in the specs I can then do: >>> >>> ? ?Derived.base_class.should_receive(:new).with(''something'') >>> ? ?d = Derived.new(''something'') >>> >>> or: >>> >>> ? ?d = Derived.new >>> ? ?d.base.should_receive(:do_something) >>> ? ?d.do_something >>> >>> Tobias >>> _______________________________________________ >>> rspec-users mailing list >>> rspec-users at rubyforge.org >>> http://rubyforge.org/mailman/listinfo/rspec-users >> > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
> > describe "count_by_params" do > > it "should pass all the params and options to #scope_by_params" do > > params = { :foo => 1, :bar => 2 } > > options = { :foo => 3, :bar => 4 } > > > > @base = ActiveRecord::Base > > @result = mock("query results", :count => 1) > > @base.expects(:scoped_by_params).with(params, > > options).returns(@result) > > @base.count_by_params(params, options) > > end > > > > Is #scoped_by_params a public method that has its own examples? Down > at the model level I would want to know that count_by_params actually > was working as expected and not just collaborating with itself to call > an internal method. I don''t know how crazy scoped_by_params is so > maybe there is a potential risk you''re willing to accept given the > complexity of what it does, but this example raises my eyebrow as to > the behaviour your actually expecting and verifying. >scoped_by_params is indeed a public method with its own examples. I agree that this spec does not say much, it is not more than a verification of its implementation i guess. In sum, I have three methods find_by_params, count_by_params and scoped_by_params. The first two rely on scoped_by_params, which in turn relies on dynamic scopes from ActiveRecord::Base. I see two possible ways of testing this; an integration test in which we focus on the result of the queries. Another possibility, the one I use in this example, is to check if other methods are called correctly. My reasoning is that ActiveRecord::Base has an extensive test suite, so I only need to test that it is called correctly. By mocking methods call I''m having simpler and faster tests. The problem is that the test by itself will not tell me when Rails internals are changed and my code has actually been broken. I''m assuming that the end result should be tested by integration tests, do you agree? What would your test strategy be? (The code is actually part of a small plugin I''m writing and can be found here http://github.com/jeroenvandijk/find_by_params/blob/5a97028c505d9254b2bcbe5e3aa5b801f7ca34ca/lib/find_by_params.rb ) Jeroen PS I''m a TDD beginner so feel free correct me :)> > > > On Thu, Mar 19, 2009 at 5:35 PM, Jeroen van Dijk > > <jeroentjevandijk at gmail.com> wrote: > >> > >> Hi all, > >> > >> I''m having the same problem as Tobi and I wanted to try Tobi mock > solution > >> but unfortunately it does not work for me. > >> > >> Here is a trivial example I want to test: > >> > >> module ActiveRecord > >> class Base > >> def self.count_by_params(params = {}, options = {}) > >> scoped_by_params(params).count > >> end > >> end > >> end > >> > >> And my spec: > >> module ActiveRecord > >> class Base > >> include BaseClassMock > >> end > >> end > >> > >> class DummyUser < ActiveRecord::Base > >> end > >> > >> it "should pass all the params and options to #scope_by_params" do > >> params = { :foo => 1, :bar => 2 } > >> options = { :foo => 3, :bar => 4 } > >> @dummy = DummyUser > >> @dummy.base_class.should_receive(:scope_by_params).with(params, > >> options) > >> DummyUser.count_by_params(params, options) > >> end > >> > >> With the above example and Tobi''s BaseClassMock module I''m getting > >> "undefined method `should_receive'' for #<Object:0x188c164>". Btw, I also > get > >> this when I do not use Tobi be it for for #<Class:0x188cdf8> instead of > >> Object. > >> > >> Any suggestions on what I''m doing wrong or a different approach? > >> > >> Cheers, > >> Jeroen > >> > >> > >> > >> On Sat, Feb 21, 2009 at 10:24 PM, Tobi <listaccount at e-tobi.net> wrote: > >>> > >>> Zach Dennis wrote: > >>> > >>> > +1 to composition over inheritance here. Mocking at different layers > >>> > of an inheritance hierarchy sounds like trouble and screams to pull > >>> > that thing apart. > >>> > >>> Good point! I''ve already tried the composition approach. It solves the > >>> testabilitiy problems, but it doesn''t feel right in this case. > >>> > >>> Inheritance seems to be the more natural approach. The C++ application > >>> will call virtual methods on the base class which should be overriden > in > >>> a > >>> derived class. So it seems to make sense to have the same inheritance > >>> tree > >>> in the Ruby counter parts. > >>> > >>> If I would use composition, I would at least need to dynamically extend > >>> the referenced base class so I can override some of the methods that > are > >>> the counter parts of the virtual C++ methods. And I would need to > expose > >>> the referenced class to the C++ code. > >>> > >>> I''ve finally found a way to kinda mock the base class in some way: > >>> > >>> module BaseClassMock > >>> attr_accessor :base > >>> > >>> def self.included(klass) > >>> class << klass > >>> @@base_class = Object.new > >>> > >>> def base_class > >>> return @@base_class > >>> end > >>> end > >>> end > >>> > >>> def initialize(*args) > >>> @@base_class.new(*args) if @@base_class.respond_to?(:new) > >>> @base = Object.new > >>> end > >>> > >>> def method_missing(symbol, *args) > >>> @base.send(symbol, *args) > >>> end > >>> end > >>> > >>> Any base class (Swig classes in my case) should then be declared for > >>> RSpec > >>> like: > >>> > >>> module Swig > >>> class Base; include BaseClassMock; end > >>> end > >>> > >>> And in the specs I can then do: > >>> > >>> Derived.base_class.should_receive(:new).with(''something'') > >>> d = Derived.new(''something'') > >>> > >>> or: > >>> > >>> d = Derived.new > >>> d.base.should_receive(:do_something) > >>> d.do_something > >>> > >>> Tobias > >>> _______________________________________________ > >>> rspec-users mailing list > >>> rspec-users at rubyforge.org > >>> http://rubyforge.org/mailman/listinfo/rspec-users > >> > > > > > > _______________________________________________ > > rspec-users mailing list > > rspec-users at rubyforge.org > > http://rubyforge.org/mailman/listinfo/rspec-users > > > > > > -- > Zach Dennis > http://www.continuousthinking.com > http://www.mutuallyhuman.com > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20090321/63af65ed/attachment.html>