#-- # Mock # # Copyright (c) 2005 Thomas Sawyer, Michael Granger and George Moschovitis # # Ruby License # # This module is free software. You may use, modify, and/or redistribute this # software under the same terms as Ruby. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # # # ========================================================================== # Revision History :: # -------------------------------------------------------------------------- # 0.5.8.24 trans Created, based-off of George's initial Mock idea, and # largely influence by Michael Granger's Test::Unit::Mock. # ========================================================================== # #++ require 'ostruct' #:title: Mock # # A straightfoward mocking facility. Typically used in test cases. # The Mock class offers a few constructors fro quickly building # mockups. # # mock - Returns a static reponse. # echo - Returns the arguments passed-in. # spin - Returns a rotation of reponses. # keys - Returns an index of responses. # # Mock classes can be built from sratch or partially framed # against other classes. # # === Usage # # class ContextMock < Mock # mock :response_headers, {} # spin :host_url, ['http://www.nitrohq.com','http://www.rubyforge.com'] # end # # ctx = ContextMock.new # ctx.response_headers['location'] = url # ctx.host_url #=> "http://www.nitrohq.com" # ctx.host_url #=> "http://www.rubyforge.com" # # Or # # class ContextMock < Mock(Context) # ... # end # class Mock < OpenStruct # include these? #include Test::Unit::Assertions # Certain methods are not mocked: # inspect (tricky) # class (delegated) # kind_of? (delegated) # is_a? (delegated) # instance_of? (delegated) # method (works as-is) # send (works as-is) # respond_to? (works as-is) # hash (no way to mock) # # __id__, __call__, etc. (not meant to be mocked, ever!) # UnmockedMethods = %r{^( |inspect |kind_of\?|is_a\?|instance_of\?|class |method|send|respond_to\? |hash |__ )}x class << self attr :mocked_class def mocks self.methods(false) end # Mock a static repsonse. def mock( sym, val ) define_method( sym ) { |*args| val } end # Responds with input. def echo( sym ) define_method( sym ) { |*args| args } end # Reponds with a rotation of reponses. def spin( sym, arr ) define_method( sym ) { |*args| arr.push(arr.shift) ; arr[-1] } end # Responds according to a mapping of input parameters. def keys( sym, hsh ) define_method( sym ) { |*args| hsh[args] } end end # Delegate methods: #class, instance_of?, kind_of?, and is_a? alias :__class :class def class # :nodoc: return __class.mocked_class end def instance_of?( klass ) # :nodoc: self.class == klass end def kind_of?( klass ) # :nodoc: self.class <= klass end alias_method :is_a?, :kind_of? end # Factory method for creating semi-functional mock objects given the # class which is to be mocked. It looks like a constant for purposes # of syntactic sugar. def Mock( realclass ) mockclass = Class.new( Mock ) mockclass.instance_eval do @mocked_class = realclass end # Provide an accessor to class instance var that holds the class # object we're faking class << mockclass # The actual class being mocked attr_reader :mocked_class # Propagate the mocked class ivar to derivatives so it can be # called like: # class MockFoo < Mock( RealClass ) def inherited( subclass ) mc = self.mockedClass subclass.instance_eval do @mocked_class = mc end end end # Build method definitions for all the mocked class's instance # methods, as well as those given to it by its superclasses, since # we're not really inheriting from it. imethods = realclass.instance_methods(true).collect do |name| next if name =~ ::Mock::UnmockedMethods # Figure out the argument list arity = realclass.instance_method( name ).arity optargs = false if arity < 0 optargs = true arity = (arity+1).abs end args = [] arity.times do |n| args << "arg#{n+1}" end args << "*optargs" if optargs # Build a method definition. Some methods need special # declarations. argsj = args.join(',') case name.intern when :initialize "def initialize(#{argsj}) ; super ; end" else "def #{name}(#{argsj}) ; self.send(#{name},#{argsj}) ; end" #"def %s( %s ) ; self.__mockRegisterCall(%s) ; end" % # [ name, argstr, [":#{name}", *args].join(',') ] end end # Now add the instance methods to the mockclass class mockclass.class_eval imethods.join( "\n" ) return mockclass end # test if $0 == __FILE__ class MyMock < Mock mock :m, 10 echo :e spin :s, [1,2,3] keys :i, { [:foo] => 'Hello', [:boo] => 'Frog' } def a(k) k+1 end end my = MyMock.new p my.m p my.e('hi') p my.s p my.s p my.s p my.i(:foo) p my.i(:boo) p my.a(1) end