module MiniSpec::InstanceAPI

Attributes

__ms__failures[R]

@return [Array]

__ms__inside_helper[RW]

Public Instance Methods

__ms__boot() click to toggle source

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
__ms__halt() click to toggle source

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
__ms__mocks__define_regular_proxy(object, method_name, visibility) click to toggle source

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
__ms__mocks__define_singleton_proxy(object, method_name) click to toggle source

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
__ms__mocks__define_void_proxy(object, method_name) click to toggle source

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
__ms__mocks__instance_messages(object) click to toggle source

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
__ms__mocks__messages_copy() click to toggle source

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
__ms__mocks__method_missing_proxy(object, method) click to toggle source

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
__ms__mocks__regular_proxy(object, method_name, method = nil) click to toggle source

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
__ms__mocks__reset_variables() click to toggle source
# 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
__ms__mocks__restore_originals() click to toggle source

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
__ms__mocks__validate_expectations() click to toggle source
# File lib/minispec/api/instance/mocks/mocks.rb, line 323
def __ms__mocks__validate_expectations
  catch(:__ms__stop_evaluation) { @__ms__expectations.each(&:validate!) }
end
__ms__prepare_test() click to toggle source

@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
__ms__run_test(label) click to toggle source
# 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
__ms__skipped?() click to toggle source
# File lib/minispec/api/instance.rb, line 66
def __ms__skipped?; @__ms__skipped end
chained_stub(object, chain, visibility = nil, &block) click to toggle source

@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
double(*args, &proc) click to toggle source

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
fail(failure = {}) click to toggle source

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
Also aliased as: fail!
fail!(failure = {})
Alias for: fail
hash_stub(object, hash, visibility = nil, &proc) click to toggle source

@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
mock(object, method, visibility = nil, &proc) click to toggle source

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
mocks(object, *methods, &proc) click to toggle source

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
private_mock(object, method, &proc) click to toggle source
# File lib/minispec/api/instance/mocks/mocks.rb, line 66
def private_mock object, method, &proc
  mock(object, method, :private, &proc)
end
private_mocks(object, *methods, &proc) click to toggle source
# 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
private_stub(object, stub, &proc) click to toggle source

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
private_stubs(object, *stubs, &proc) click to toggle source
# 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
protected_mock(object, method, &proc) click to toggle source
# File lib/minispec/api/instance/mocks/mocks.rb, line 58
def protected_mock object, method, &proc
  mock(object, method, :protected, &proc)
end
protected_mocks(object, *methods, &proc) click to toggle source
# 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
protected_stub(object, stub, &proc) click to toggle source

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
protected_stubs(object, *stubs, &proc) click to toggle source
# 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
proxy(object, method_name) click to toggle source

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
public_mock(object, method, &proc) click to toggle source

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
public_mocks(object, *methods, &proc) click to toggle source
# 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
public_stub(object, stub, &proc) click to toggle source

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
public_stubs(object, *stubs, &proc) click to toggle source
# 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
skip() click to toggle source

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
Also aliased as: skip!
skip!()
Alias for: skip
spy(object, *methods) click to toggle source

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
stub(object, stub, visibility = nil, &proc) click to toggle source

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
stubs(object, *stubs, &proc) click to toggle source

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
subject() click to toggle source

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