module MiniSpec::InstanceAPI
Attributes
@return [Array]
Public Instance Methods
runs before any tests
# File lib/minispec/api/instance.rb, line 145 def __ms__boot __ms__prepare_test (hook = self.class.before_all?) && self.instance_exec(&hook) end
runs after all tests finished. runs unconditionally even when there are failed tests.
# File lib/minispec/api/instance.rb, line 152 def __ms__halt (hook = self.class.after_all?) && self.instance_exec(&hook) end
replaces given method with a proxy that collects received messages and calls the original method.
@param object @param method_name @param visibility
# File lib/minispec/api/instance/mocks/mocks.rb, line 120 def __ms__mocks__define_regular_proxy object, method_name, visibility method = object.method(method_name).unbind method = __ms__mocks__regular_proxy(object, method_name, method) extender = Module.new do define_method(method_name, &method) private method_name if visibility == :private protected method_name if visibility == :protected end object.extend(extender) end
defines a singleton proxy that collects received messages and calls the original method.
@param object @param method_name
# File lib/minispec/api/instance/mocks/mocks.rb, line 136 def __ms__mocks__define_singleton_proxy object, method_name method = object.method(method_name).unbind method = __ms__mocks__regular_proxy(object, method_name, method) object.define_singleton_method(method_name, &method) end
defines a singleton proxy that collects received messages and calls nothing.
@note registering methods added this way so they can be undefined after test run
@param object @param method_name
# File lib/minispec/api/instance/mocks/mocks.rb, line 149 def __ms__mocks__define_void_proxy object, method_name (@__ms__stubs__originals[object] ||= {})[method_name] = [] method = __ms__mocks__regular_proxy(object, method_name) object.define_singleton_method(method_name, &method) end
takes a copy of received messages and returns only messages received by given object
@param object
# File lib/minispec/api/instance/mocks/mocks.rb, line 319 def __ms__mocks__instance_messages object __ms__mocks__messages_copy.select {|m| m[:object] == object}.freeze end
it is critical to iterate over a “statical” copy of messages array, otherwise iteration will generate a uncatchable infinite loop when messages array are updated during iteration.
# File lib/minispec/api/instance/mocks/mocks.rb, line 311 def __ms__mocks__messages_copy @__ms__messages.dup end
replace ‘method_missing` method with a proxy that collects received messages and calls original `method_missing` method. stat are collected for two methods:
1. `method_missing` itself 2. method what `method_missing` received as first argument
stat has same format as on ‘__ms__mocks__regular_proxy` @see (#__ms__mocks__regular_proxy)
@param object @param method [UnboundMethod] original ‘method_missing` method, unbounded
# File lib/minispec/api/instance/mocks/mocks.rb, line 220 def __ms__mocks__method_missing_proxy object, method messages = @__ms__messages Proc.new do |meth, *args, &block| message = { object: object, method: :method_missing, arguments: [meth, *args], caller: Array(caller) } messages.push(message) message = {object: object, method: meth, arguments: args} messages.push(message) proc = if block Proc.new do |*a,&b| message[:yielded] = a block.call(*a,&b) end else nil end begin message[:returned] = method.bind(self).call(meth, *args, &proc) rescue Exception => e message[:raised] = e end message.freeze message[:raised] ? raise(message[:raised]) : message[:returned] end end
returns a proc to be used with ‘define_method`. the proc will collect received messages then will call original method, if any.
messages are stored into ‘@__ms__messages` Array each single message looks like:
{object: ..., method: ..., arguments: ..., returned: ..., raised: ..., yielded: ...}
‘:returned` key are filled if original method called and it does not throw nor raise. `:raised` key are filled if original method called and it raises an error. `:yielded` key are filled if original method called with a block that was yielded.
@param object @param method_name @param method [UnboundMethod] original method, unbounded, to be called after stat collected.
if `nil`, there are two scenarios: 1. if method name is `:nil?` it returns `self == nil` after stat collected 2. otherwise it simply returns after stat collected
# File lib/minispec/api/instance/mocks/mocks.rb, line 171 def __ms__mocks__regular_proxy object, method_name, method = nil method_name.is_a?(Symbol) || raise(ArgumentError, 'method name should be a Symbol') if :method_missing == method_name return __ms__mocks__method_missing_proxy(object, method) end messages = @__ms__messages Proc.new do |*args, &block| message = { object: object, method: method_name, arguments: args, caller: Array(caller) } messages.push(message) return self == nil if method_name == :nil? return unless method proc = if block Proc.new do |*a,&b| message[:yielded] = a block.call(*a,&b) end else nil end begin message[:returned] = method.bind(self).call(*args, &proc) rescue Exception => e message[:raised] = e end message.freeze message[:raised] ? raise(message[:raised]) : message[:returned] end end
# File lib/minispec/api/instance.rb, line 105 def __ms__mocks__reset_variables @__ms__messages = [] @__ms__proxies = {} @__ms__stubs = {} @__ms__stubs__originals = {} @__ms__expectations = [] end
restoring stubbed methods.
it processes ‘@__ms__stubs__originals` Hash where keys are the objects and values are the object’s methods to be restored. each value is a Array where first element is the method name and the second element is what previous method was.
-
if second element is an empty Array that mean method were not defined before stubbing, so simply undefine it.
-
if second element is a Array with last element set to :singleton Symbol, the method was a singleton before stubbing it, so defining a singleton method using second element’s first element.
-
if second element is a Array with last element set to any of :public, :protected, :private Symbol an method with according visibility will be defined using second element’s first element.
@example there was no ‘x` method before stubbing
# => {#<Object:0x007f92bb2b52c8>=>{:x=>[]}}
@example ‘a` method was a singleton before stubbing
# => {#<Object:0x007f92bb2c5998>=>{:a=>[#<Method: #<Object:0x007f92bb2c5998>.a>, :singleton]}}
@example ‘a` was a public method before stubbing
# => {#<#<Class:0x007f92bb2cdbe8>:0x007f92bb2cd850>=>{:a=>[#<Method: #<Class:0x007f92bb2cdbe8>#a>, :public]}}
# File lib/minispec/api/instance/mocks/mocks.rb, line 281 def __ms__mocks__restore_originals return unless stubs = @__ms__stubs__originals stubs.each_pair do |object, methods| methods.each_pair do |method_name, method| # clearing proxies cache so the method can be proxied again during current test (x = @__ms__proxies[object]) && x.delete(method_name) if method.last.nil? MiniSpec::Utils.undefine_method(object, method_name) elsif method.last == :singleton object.define_singleton_method(method_name, &method.first) else extender = Module.new do define_method(method_name, &method.first) private method_name if method.last == :private protected method_name if method.last == :protected end object.extend(extender) end end end # clearing cache for cases when this run during current test stubs.clear end
# File lib/minispec/api/instance/mocks/mocks.rb, line 323 def __ms__mocks__validate_expectations catch(:__ms__stop_evaluation) { @__ms__expectations.each(&:validate!) } end
@api private setting/resetting all necessary instance variables for a test to run in clean state
# File lib/minispec/api/instance.rb, line 98 def __ms__prepare_test @__ms__vars = {} @__ms__failures = [] @__ms__skipped = nil __ms__mocks__reset_variables end
# File lib/minispec/api/instance.rb, line 113 def __ms__run_test label Minispec.tests += 1 __ms__prepare_test runner = proc do # running :before hooks, if any self.class.before?(label).each {|(l,m,b)| instance_exec(l,m,&b)} # running test catch :__ms__stop_evaluation do instance_exec(&self.class.tests[label].last) end # running :after hooks, if any self.class.after?(label).each {|(l,m,b)| instance_exec(l,m,&b)} end if around = self.class.around?(label).last self.instance_exec(runner, &around.last) else runner.call end __ms__mocks__validate_expectations __ms__mocks__restore_originals @__ms__failures rescue Exception => e [e] ensure __ms__mocks__reset_variables end
# File lib/minispec/api/instance.rb, line 66 def __ms__skipped?; @__ms__skipped end
@example define a chained stub
stub(obj, 'a.b.c')
# File lib/minispec/api/instance/mocks/stubs.rb, line 52 def chained_stub object, chain, visibility = nil, &block chain = chain.split('.').map(&:to_sym) base, last_index = self, chain.size - 1 chain.each_with_index do |m,i| next_object = (i == last_index ? nil : Struct.new(chain[i+1]).new) return stub(object, m, visibility, &block) unless next_object stub(object, m, visibility) { next_object } object = next_object end end
creates a double object. if one or more arguments given, first argument will be used as name, unless it is a Hash. arguments that goes after first one are treated as stubs.
@example create a double that will respond to ‘color` and reported as :apple
apple = double(:apple, :color) { 'Red' } apple.color # => Red
@example injecting a double into a real battle and expecting it to receive some messages
user = double(:user, :name, :address) expect(user).to_receive(:name, :address) Shipping.new.get_address_for(user)
@example spy on a double
user = double(:user, :name, :address) Shipping.new.get_address_for(user) assert(user).received(:name, :address)
# File lib/minispec/api/instance/mocks/doubles.rb, line 22 def double *args, &proc name = args.first.is_a?(Hash) ? nil : args.shift object = Object.new object.define_singleton_method(:__ms__double_instance) {true} object.define_singleton_method(:inspect) {name} if name hashes, rest = args.partition {|s| s.is_a?(Hash)} hashes.each {|h| stub(object, h)} rest.each {|s| stub(object, s, &proc)} object end
adds a new failure to the stack. a failure is a Hash containing keys like
:message :left_method :left_object :right_method :right_object :negation :callers
used by MiniSpec::Run#failures_summary and MiniSpecRun#failure_message to output useful info about failed tests.
@param failure if failure is ‘nil` or `false` it simply returns.
unless failure is a Hash it is building a Hash like {message: failure} and adding it to the stack.
@return [Array] failures stack
# File lib/minispec/api/instance.rb, line 84 def fail failure = {} return unless failure unless failure.is_a?(Hash) failure || raise(ArgumentError, 'Please provide a failure message') failure = {message: failure} end @__ms__failures << failure.merge(callers: @__ms__callers) throw :__ms__stop_evaluation unless self.class.continue_on_failures? end
@example make ‘obj.a` to return :x and `obj.b` to return :y
stub(obj, :a => :x, :b => :y)
# File lib/minispec/api/instance/mocks/stubs.rb, line 41 def hash_stub object, hash, visibility = nil, &proc proc && raise(ArgumentError, 'Both Hash and block given. Please use either one.') hash.each_pair do |s,v| stub(object, s, visibility, &proc {v}) end return MiniSpec::Mocks::HashedStub end
the mock is basically a stub with difference it will also add a expectation. that’s it, a mock will stub a method on a object and will expect that stub to be called before test finished.
the ‘mock` method will return the actual stub so you can build chained constraints on it.
@note if mocked method exists it’s visibility will be kept
@example make ‘some_object` to respond to `:some_method`
and expect `:some_method` to be called before current test finished. also make `:some_method` to behave differently depending on given arguments. so if called with [:a, :b] arguments it will return 'called with a, b'. called with [:x, :y] arguments it will return 'called with x, y'. called with any other arguments or without arguments at all it returns 'whatever'. mock(some_object, :some_method). with(:a, :b) { 'called with a, b' }. with(:x, :y) { 'called with x, y' }. with_any { 'whatever' }
# File lib/minispec/api/instance/mocks/mocks.rb, line 25 def mock object, method, visibility = nil, &proc if method.is_a?(Hash) proc && raise(ArgumentError, 'Both Hash and block given. Please use either one.') method.each_pair {|m,r| mock(object, m, visibility, &proc {r})} return MiniSpec::Mocks::HashedStub end visibility ||= MiniSpec::Utils.method_visibility(object, method) || :public # IMPORTANT! stub should be defined before expectation stub = stub(object, method, visibility, &proc) expect(object).to_receive(method) stub end
mocking multiple methods at once
@param object @param *methods @param &proc @return MiniSpec::Mocks::MultipleStubsProxy
instance
# File lib/minispec/api/instance/mocks/mocks.rb, line 45 def mocks object, *methods, &proc MiniSpec::Mocks::MultipleStubsProxy.new(methods.map {|m| mock(object, m, &proc)}) end
# File lib/minispec/api/instance/mocks/mocks.rb, line 66 def private_mock object, method, &proc mock(object, method, :private, &proc) end
# File lib/minispec/api/instance/mocks/mocks.rb, line 70 def private_mocks object, *methods, &proc MiniSpec::Mocks::MultipleStubsProxy.new(methods.map {|m| mock(object, m, :private, &proc)}) end
same as stub except it defines private stubs (@see stub
)
# File lib/minispec/api/instance/mocks/stubs.rb, line 97 def private_stub object, stub, &proc stub(object, stub, :private, &proc) end
# File lib/minispec/api/instance/mocks/stubs.rb, line 101 def private_stubs object, *stubs, &proc MiniSpec::Mocks::MultipleStubsProxy.new(stubs.map {|s| private_stub(object, s, &proc)}) end
# File lib/minispec/api/instance/mocks/mocks.rb, line 58 def protected_mock object, method, &proc mock(object, method, :protected, &proc) end
# File lib/minispec/api/instance/mocks/mocks.rb, line 62 def protected_mocks object, *methods, &proc MiniSpec::Mocks::MultipleStubsProxy.new(methods.map {|m| mock(object, m, :protected, &proc)}) end
same as stub except it defines protected stubs (@see stub
)
# File lib/minispec/api/instance/mocks/stubs.rb, line 87 def protected_stub object, stub, &proc stub(object, stub, :protected, &proc) end
# File lib/minispec/api/instance/mocks/stubs.rb, line 91 def protected_stubs object, *stubs, &proc MiniSpec::Mocks::MultipleStubsProxy.new(stubs.map {|s| protected_stub(object, s, &proc)}) end
overriding given method of given object with a proxy so MiniSpec
can later check whether given method was called.
if given method does not exists a NoMethodError raised
@note doubles and stubs will be skipped as they are already proxified
@example
proxy(obj, :a) assert(obj).received(:a) # checking whether obj received :a message
@param object @param method_name
# File lib/minispec/api/instance/mocks/mocks.rb, line 87 def proxy object, method_name # do not proxify doubles return if object.respond_to?(:__ms__double_instance) # do not proxify stubs return if (x = @__ms__stubs__originals) && (x = x[object]) && x[method_name] proxies = (@__ms__proxies[object] ||= []) return if proxies.include?(method_name) proxies << method_name # method exists and it is a singleton. # `nil?` method can be overridden only through a singleton if method_name == :nil? || object.singleton_methods.include?(method_name) return __ms__mocks__define_singleton_proxy(object, method_name) end # method exists and it is not a singleton, define a regular proxy if visibility = MiniSpec::Utils.method_visibility(object, method_name) return __ms__mocks__define_regular_proxy(object, method_name, visibility) end raise(NoMethodError, '%s does not respond to %s. Can not proxify an un-existing method.' % [ object.inspect, method_name.inspect ]) end
same as ‘mock` except it will enforce public visibility on mocked method.
# File lib/minispec/api/instance/mocks/mocks.rb, line 50 def public_mock object, method, &proc mock(object, method, :public, &proc) end
# File lib/minispec/api/instance/mocks/mocks.rb, line 54 def public_mocks object, *methods, &proc MiniSpec::Mocks::MultipleStubsProxy.new(methods.map {|m| public_mock(object, m, &proc)}) end
stubbing a method and enforce public visibility on it. that’s it, even if method exists and it is not public, after stubbing it will become public.
# File lib/minispec/api/instance/mocks/stubs.rb, line 77 def public_stub object, stub, &proc stub(object, stub, :public, &proc) end
# File lib/minispec/api/instance/mocks/stubs.rb, line 81 def public_stubs object, *stubs, &proc MiniSpec::Mocks::MultipleStubsProxy.new(stubs.map {|s| public_stub(object, s, &proc)}) end
stop evaluation of the current test right away
@example
test :some_test do is(1) < 2 skip is(1) > 2 # this wont be evaluated so the test will pass end
# File lib/minispec/api/instance.rb, line 60 def skip @__ms__skipped = caller.first throw :__ms__stop_evaluation end
basically by proxying an object we attach a spy on it so any received messages will be reported
@example spying user for :login and :logout messages
user = User.new spy(user, :login, :logout) # ... assert(user).received(:login, :logout)
# File lib/minispec/api/instance/mocks/spies.rb, line 13 def spy object, *methods methods.each {|method| proxy(object, method)} end
stubbing given method and keeps original visibility
if a block given, it will receive original method as first argument and any passed parameters as rest arguments.
@example make Some::Remote::API.call to return success
stub(Some::Remote::API, :call) { :success }
@example call original
stub(obj, :a) {|orig| orig.call}
@param object object to define stub on @param stub method to be stubbed.
if a Hash given, keys will be stubbed methods and values return values
@param [Proc] &proc block to be yielded when stub called @return MiniSpec::Mocks::Stub
instance
# File lib/minispec/api/instance/mocks/stubs.rb, line 21 def stub object, stub, visibility = nil, &proc [Symbol, String, Hash].include?(stub.class) || raise(ArgumentError, 'a Symbol, String or Hash expected') if stub.is_a?(Hash) return hash_stub(object, stub, visibility, &proc) elsif stub =~ /\./ return chained_stub(object, stub, visibility, &proc) end visibility ||= MiniSpec::Utils.method_visibility(object, stub) || :public stubs = (@__ms__stubs[object.__id__] ||= {}) stubs[stub] ||= MiniSpec::Mocks::Stub.new(object, @__ms__messages, @__ms__stubs__originals) stubs[stub].stubify(stub, visibility, &proc) stubs[stub] end
same as ‘stub` except it defines multiple stubs at once
@param object @param *stubs @param &proc @return MiniSpec::Mocks::MultipleStubsProxy
instance
# File lib/minispec/api/instance/mocks/stubs.rb, line 70 def stubs object, *stubs, &proc MiniSpec::Mocks::MultipleStubsProxy.new(stubs.map {|s| stub(object, s, &proc)}) end
this will be overridden when the spec are defined using Minispec’s DSL
@example
describe Hash do # subject will be set to Hash it 'responds to :[]' do assert.respond_to?(:[]) # same as assert(Hash).respond_to?(:[]) end end
# File lib/minispec/api/instance.rb, line 49 def subject; end