Module: MiniSpec::InstanceAPI

Defined in:
lib/minispec/api/instance.rb,
lib/minispec/api/instance/mocks/mocks.rb,
lib/minispec/api/instance/mocks/spies.rb,
lib/minispec/api/instance/mocks/stubs.rb,
lib/minispec/api/instance/mocks/doubles.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#__ms__failuresArray (readonly)

Returns:

  • (Array)


5
6
7
# File 'lib/minispec/api/instance.rb', line 5

def __ms__failures
  @__ms__failures
end

#__ms__inside_helperObject

Returns the value of attribute __ms__inside_helper.



7
8
9
# File 'lib/minispec/api/instance.rb', line 7

def __ms__inside_helper
  @__ms__inside_helper
end

Instance Method Details

#__ms__bootObject

runs before any tests



145
146
147
148
# 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__haltObject

runs after all tests finished. runs unconditionally even when there are failed tests.



152
153
154
# 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) ⇒ Object

replaces given method with a proxy that collects received messages and calls the original method.

Parameters:

  • object
  • method_name
  • visibility


120
121
122
123
124
125
126
127
128
129
# 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) ⇒ Object

defines a singleton proxy that collects received messages and calls the original method.

Parameters:

  • object
  • method_name


136
137
138
139
140
# 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) ⇒ Object

Note:

registering methods added this way so they can be undefined after test run

defines a singleton proxy that collects received messages and calls nothing.

Parameters:

  • object
  • method_name


149
150
151
152
153
# 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) ⇒ Object

takes a copy of received messages and returns only messages received by given object

Parameters:

  • object


319
320
321
# 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_copyObject

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.



311
312
313
# 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) ⇒ Object

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`

Parameters:

  • object
  • method (UnboundMethod)

    original ‘method_missing` method, unbounded

See Also:

  • MiniSpec::InstanceAPI.((#__ms__mocks__regular_proxy)


220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# 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) ⇒ Object

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.

Parameters:

  • object
  • method_name
  • method (UnboundMethod) (defaults to: nil)

    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



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# 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_variablesObject



105
106
107
108
109
110
111
# 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_originalsObject

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.

Examples:

there was no ‘x` method before stubbing


# => {#<Object:0x007f92bb2b52c8>=>{:x=>[]}}

‘a` method was a singleton before stubbing


# => {#<Object:0x007f92bb2c5998>=>{:a=>[#<Method: #<Object:0x007f92bb2c5998>.a>, :singleton]}}

‘a` was a public method before stubbing


# => {#<#<Class:0x007f92bb2cdbe8>:0x007f92bb2cd850>=>{:a=>[#<Method: #<Class:0x007f92bb2cdbe8>#a>, :public]}}


281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# 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_expectationsObject



323
324
325
# 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_testObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

setting/resetting all necessary instance variables for a test to run in clean state



98
99
100
101
102
103
# 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) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# 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?Boolean

Returns:

  • (Boolean)


66
# File 'lib/minispec/api/instance.rb', line 66

def __ms__skipped?; @__ms__skipped end

#chained_stub(object, chain, visibility = nil, &block) ⇒ Object

Examples:

define a chained stub

stub(obj, 'a.b.c')


52
53
54
55
56
57
58
59
60
61
# 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) ⇒ Object

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.

Examples:

create a double that will respond to ‘color` and reported as :apple

apple = double(:apple, :color) { 'Red' }
apple.color # => Red

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)

spy on a double

user = double(:user, :name, :address)
Shipping.new.get_address_for(user)
assert(user).received(:name, :address)


22
23
24
25
26
27
28
29
30
31
32
33
34
# 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 = {}) ⇒ Array Also known as: fail!

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.

Parameters:

  • failure (defaults to: {})

    if failure is ‘nil` or `false` it simply returns. unless failure is a Hash it is building a Hash like failure and adding it to the stack.

Returns:

  • (Array)

    failures stack



84
85
86
87
88
89
90
91
92
# 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

#hash_stub(object, hash, visibility = nil, &proc) ⇒ Object

Examples:

make ‘obj.a` to return :x and `obj.b` to return :y

stub(obj, :a => :x, :b => :y)


41
42
43
44
45
46
47
# 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) ⇒ Object

Note:

if mocked method exists it’s visibility will be kept

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.

mock(some_object, :some_method).
  with(:a, :b) { 'called with a, b' }.
  with(:x, :y) { 'called with x, y' }.
  with_any { 'whatever' }

Examples:

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'.


25
26
27
28
29
30
31
32
33
34
35
36
# 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) ⇒ Object

mocking multiple methods at once

Parameters:

  • object
  • *methods
  • &proc

Returns:

  • MiniSpec::Mocks::MultipleStubsProxy instance



45
46
47
# 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) ⇒ Object



66
67
68
# 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) ⇒ Object



70
71
72
# 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) ⇒ Object

same as stub except it defines private stubs (@see #stub)



97
98
99
# 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) ⇒ Object



101
102
103
# 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) ⇒ Object



58
59
60
# 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) ⇒ Object



62
63
64
# 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) ⇒ Object

same as stub except it defines protected stubs (@see #stub)



87
88
89
# 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) ⇒ Object



91
92
93
# 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) ⇒ Object

Note:

doubles and stubs will be skipped as they are already proxified

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

Examples:

proxy(obj, :a)
assert(obj).received(:a)  # checking whether obj received :a message

Parameters:

  • object
  • method_name

Raises:

  • (NoMethodError)


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# 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) ⇒ Object

same as ‘mock` except it will enforce public visibility on mocked method.



50
51
52
# 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) ⇒ Object



54
55
56
# 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) ⇒ Object

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.



77
78
79
# 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) ⇒ Object



81
82
83
# 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

#skipObject Also known as: skip!

stop evaluation of the current test right away

Examples:

test :some_test do
  is(1) < 2
  skip
  is(1) > 2 # this wont be evaluated so the test will pass
end


60
61
62
63
# File 'lib/minispec/api/instance.rb', line 60

def skip
  @__ms__skipped = caller.first
  throw :__ms__stop_evaluation
end

#spy(object, *methods) ⇒ Object

basically by proxying an object we attach a spy on it so any received messages will be reported

Examples:

spying user for :login and :logout messages

user = User.new
spy(user, :login, :logout)
# ...
assert(user).received(:login, :logout)


13
14
15
# 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) ⇒ Object

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.

Examples:

make Some::Remote::API.call to return success

stub(Some::Remote::API, :call) { :success }

call original

stub(obj, :a) {|orig| orig.call}

Parameters:

  • object

    object to define stub on

  • stub

    method to be stubbed. if a Hash given, keys will be stubbed methods and values return values

  • &proc (Proc)

    block to be yielded when stub called

Returns:

  • MiniSpec::Mocks::Stub instance



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 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) ⇒ Object

same as ‘stub` except it defines multiple stubs at once

Parameters:

  • object
  • *stubs
  • &proc

Returns:

  • MiniSpec::Mocks::MultipleStubsProxy instance



70
71
72
# 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

#subjectObject

this will be overridden when the spec are defined using Minispec’s DSL

Examples:

describe Hash do
  # subject will be set to Hash
  it 'responds to :[]' do
    assert.respond_to?(:[]) # same as assert(Hash).respond_to?(:[])
  end
end


49
# File 'lib/minispec/api/instance.rb', line 49

def subject; end