lib/much-stub.rb in much-stub-0.1.2 vs lib/much-stub.rb in much-stub-0.1.3

- old
+ new

@@ -1,16 +1,25 @@ require "much-stub/version" +require "much-stub/call" +require "much-stub/call_spy" + module MuchStub def self.stubs @stubs ||= {} end def self.stub_key(obj, meth) MuchStub::Stub.key(obj, meth) end + def self.arity_matches?(method, args) + return true if method.arity == args.size # mandatory args + return true if method.arity < 0 && args.size >= (method.arity+1).abs # variable args + return false + end + def self.call(*args, &block) self.stub(*args, &block) end def self.stub(obj, meth, &block) @@ -42,10 +51,26 @@ tap_block.call(value, *args, &block) if tap_block } } end + def self.tap_on_call(obj, meth, &on_call_block) + self.tap(obj, meth) { |value, *args, &block| + on_call_block.call(value, MuchStub::Call.new(*args, &block)) if on_call_block + } + end + + def self.spy(obj, *meths, **return_values) + MuchStub::CallSpy.new(**return_values).tap do |spy| + meths.each do |meth| + self.stub(obj, meth) { |*args, &block| + spy.public_send(meth, *args, &block) + } + end + end + end + class Stub def self.key(object, method_name) "--#{object.object_id}--#{method_name}--" end @@ -73,33 +98,51 @@ @method.call(*args, &block) end def call(args, orig_caller = nil, &block) orig_caller ||= caller_locations - unless arity_matches?(args) - msg = "arity mismatch on `#{@method_name}`: " \ - "expected #{number_of_args(@method.arity)}, " \ - "called with #{args.size}" - raise StubArityError, msg, orig_caller.map(&:to_s) + unless MuchStub.arity_matches?(@method, args) + raise( + StubArityError.new( + @method, + args, + method_name: @method_name, + backtrace: orig_caller)) end lookup(args, orig_caller).call(*args, &block) rescue NotStubbedError @lookup.rehash lookup(args, orig_caller).call(*args, &block) end def with(*args, &block) orig_caller = caller_locations - unless arity_matches?(args) - msg = "arity mismatch on `#{@method_name}`: " \ - "expected #{number_of_args(@method.arity)}, " \ - "stubbed with #{args.size}" - raise StubArityError, msg, orig_caller.map(&:to_s) + unless MuchStub.arity_matches?(@method, args) + raise( + StubArityError.new( + @method, + args, + method_name: @method_name, + backtrace: orig_caller)) end @lookup[args] = block + self end + def on_call(&on_call_block) + stub_block = + ->(*args, &block) { + on_call_block.call(MuchStub::Call.new(*args, &block)) if on_call_block + } + if @lookup.empty? + @do = stub_block + elsif @lookup.has_value?(nil) + @lookup.transform_values!{ |value| value.nil? ? stub_block : value } + end + self + end + def teardown @metaclass.send(:undef_method, @method_name) MuchStub.send(:remove_instance_variable, @ivar_name) @metaclass.send(:alias_method, @method_name, @name) @metaclass.send(:undef_method, @name) @@ -141,46 +184,56 @@ end stub_method end def lookup(args, orig_caller) - @lookup.fetch(args) do + @lookup.fetch(args) { self.do || begin msg = "#{inspect_call(args)} not stubbed." inspect_lookup_stubs.tap do |stubs| msg += "\nStubs:\n#{stubs}" if !stubs.empty? end raise NotStubbedError, msg, orig_caller.map(&:to_s) end - end + } || + raise( + StubError, + "#{inspect_call(args)} stubbed with no block.", + orig_caller.map(&:to_s)) end - def arity_matches?(args) - return true if @method.arity == args.size # mandatory args - return true if @method.arity < 0 && args.size >= (@method.arity+1).abs # variable args - return false - end - def inspect_lookup_stubs @lookup.keys.map{ |args| " - #{inspect_call(args)}" }.join("\n") end def inspect_call(args) "`#{@method_name}(#{args.map(&:inspect).join(",")})`" end - - def number_of_args(arity) - if arity < 0 - "at least #{(arity + 1).abs}" - else - arity - end - end end StubError = Class.new(ArgumentError) NotStubbedError = Class.new(StubError) - StubArityError = Class.new(StubError) + StubArityError = + Class.new(StubError) do + def initialize(method, args, method_name:, backtrace:) + msg = "arity mismatch on `#{method_name}`: " \ + "expected #{number_of_args(method.arity)}, " \ + "called with #{args.size}" + + super(msg) + set_backtrace(Array(backtrace).map(&:to_s)) + end + + private + + def number_of_args(arity) + if arity < 0 + "at least #{(arity + 1).abs}" + else + arity + end + end + end NullStub = Class.new do def teardown; end # no-op end