minispec 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.pryrc +2 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.md +2140 -0
  6. data/Rakefile +11 -0
  7. data/bin/minispec +4 -0
  8. data/lib/minispec.rb +175 -0
  9. data/lib/minispec/api.rb +2 -0
  10. data/lib/minispec/api/class.rb +195 -0
  11. data/lib/minispec/api/class/after.rb +49 -0
  12. data/lib/minispec/api/class/around.rb +54 -0
  13. data/lib/minispec/api/class/before.rb +101 -0
  14. data/lib/minispec/api/class/helpers.rb +116 -0
  15. data/lib/minispec/api/class/let.rb +44 -0
  16. data/lib/minispec/api/class/tests.rb +33 -0
  17. data/lib/minispec/api/instance.rb +158 -0
  18. data/lib/minispec/api/instance/mocks/doubles.rb +36 -0
  19. data/lib/minispec/api/instance/mocks/mocks.rb +319 -0
  20. data/lib/minispec/api/instance/mocks/spies.rb +17 -0
  21. data/lib/minispec/api/instance/mocks/stubs.rb +105 -0
  22. data/lib/minispec/helpers.rb +1 -0
  23. data/lib/minispec/helpers/array.rb +56 -0
  24. data/lib/minispec/helpers/booleans.rb +108 -0
  25. data/lib/minispec/helpers/generic.rb +24 -0
  26. data/lib/minispec/helpers/mocks/expectations.rb +29 -0
  27. data/lib/minispec/helpers/mocks/spies.rb +36 -0
  28. data/lib/minispec/helpers/raise.rb +44 -0
  29. data/lib/minispec/helpers/throw.rb +29 -0
  30. data/lib/minispec/mocks.rb +11 -0
  31. data/lib/minispec/mocks/expectations.rb +77 -0
  32. data/lib/minispec/mocks/stubs.rb +178 -0
  33. data/lib/minispec/mocks/validations.rb +80 -0
  34. data/lib/minispec/mocks/validations/amount.rb +63 -0
  35. data/lib/minispec/mocks/validations/arguments.rb +161 -0
  36. data/lib/minispec/mocks/validations/caller.rb +43 -0
  37. data/lib/minispec/mocks/validations/order.rb +47 -0
  38. data/lib/minispec/mocks/validations/raise.rb +111 -0
  39. data/lib/minispec/mocks/validations/return.rb +74 -0
  40. data/lib/minispec/mocks/validations/throw.rb +91 -0
  41. data/lib/minispec/mocks/validations/yield.rb +141 -0
  42. data/lib/minispec/proxy.rb +201 -0
  43. data/lib/minispec/reporter.rb +185 -0
  44. data/lib/minispec/utils.rb +139 -0
  45. data/lib/minispec/utils/differ.rb +325 -0
  46. data/lib/minispec/utils/pretty_print.rb +51 -0
  47. data/lib/minispec/utils/raise.rb +123 -0
  48. data/lib/minispec/utils/throw.rb +140 -0
  49. data/minispec.gemspec +27 -0
  50. data/test/mocks/expectations/amount.rb +67 -0
  51. data/test/mocks/expectations/arguments.rb +126 -0
  52. data/test/mocks/expectations/caller.rb +55 -0
  53. data/test/mocks/expectations/generic.rb +35 -0
  54. data/test/mocks/expectations/order.rb +46 -0
  55. data/test/mocks/expectations/raise.rb +166 -0
  56. data/test/mocks/expectations/return.rb +71 -0
  57. data/test/mocks/expectations/throw.rb +113 -0
  58. data/test/mocks/expectations/yield.rb +109 -0
  59. data/test/mocks/spies/amount.rb +68 -0
  60. data/test/mocks/spies/arguments.rb +57 -0
  61. data/test/mocks/spies/generic.rb +61 -0
  62. data/test/mocks/spies/order.rb +38 -0
  63. data/test/mocks/spies/raise.rb +158 -0
  64. data/test/mocks/spies/return.rb +71 -0
  65. data/test/mocks/spies/throw.rb +113 -0
  66. data/test/mocks/spies/yield.rb +101 -0
  67. data/test/mocks/test__doubles.rb +98 -0
  68. data/test/mocks/test__expectations.rb +27 -0
  69. data/test/mocks/test__mocks.rb +197 -0
  70. data/test/mocks/test__proxies.rb +61 -0
  71. data/test/mocks/test__spies.rb +43 -0
  72. data/test/mocks/test__stubs.rb +427 -0
  73. data/test/proxified_asserts.rb +34 -0
  74. data/test/setup.rb +53 -0
  75. data/test/test__around.rb +58 -0
  76. data/test/test__assert.rb +510 -0
  77. data/test/test__before_and_after.rb +117 -0
  78. data/test/test__before_and_after_all.rb +71 -0
  79. data/test/test__helpers.rb +197 -0
  80. data/test/test__raise.rb +104 -0
  81. data/test/test__skip.rb +41 -0
  82. data/test/test__throw.rb +103 -0
  83. metadata +196 -0
@@ -0,0 +1 @@
1
+ Dir[File.expand_path('../helpers/**/*.rb', __FILE__)].each {|f| require(f)}
@@ -0,0 +1,56 @@
1
+ module Minispec
2
+
3
+ # checks whether 2 arrays has same elements.
4
+ # basically it is an unordered `==`
5
+ helper :same_elements, with_context: true do |left, right, context|
6
+ lm, rm = [left, right].map do |a|
7
+ a.is_a?(Array) ?
8
+ MiniSpec::Utils.array_elements_map(a) :
9
+ raise(ArgumentError, 'Is %s an Array?' % a.inspect)
10
+ end
11
+ failure = '%s should %%s have same elements as %s' % [left, right].map(&:inspect)
12
+ context[:negation] ?
13
+ (lm == rm && fail(failure % 'NOT')) :
14
+ (lm == rm || fail(failure % ''))
15
+ end
16
+ alias_helper :same_elements?, :same_elements
17
+ alias_helper :same_elements_as, :same_elements
18
+ alias_helper :same_elements_as?, :same_elements
19
+
20
+ # checks whether given array contains ALL of given element(s).
21
+ # if given element is a regexp, at least one element in array should match it.
22
+ helper :contain?, with_context: true do |left, *args|
23
+ context = args.pop
24
+ left.is_a?(Array) || raise(ArgumentError, 'Is %s an Array?' % left.inspect)
25
+ contain = args.all? do |a|
26
+ a.is_a?(Regexp)?
27
+ left.find {|b| b.to_s =~ a} :
28
+ left.find {|b| b == a}
29
+ end
30
+ failure = '%s should %%s contain %s' % [left, args].map(&:inspect)
31
+ context[:negation] ?
32
+ (contain && fail(failure % 'NOT')) :
33
+ (contain || fail(failure % ''))
34
+ end
35
+ alias_helper :contains?, :contain?
36
+ alias_helper :to_contain, :contain?
37
+
38
+ # checks whether given array contains at least one of given element(s).
39
+ # regular expressions accepted.
40
+ helper :contain_any?, with_context: true do |left, *args|
41
+ context = args.pop
42
+ left.is_a?(Array) || raise(ArgumentError, 'Is %s an Array?' % left.inspect)
43
+ contain = args.any? do |a|
44
+ a.is_a?(Regexp)?
45
+ left.find {|b| b.to_s =~ a} :
46
+ left.find {|b| b == a}
47
+ end
48
+ failure = '%s should %%s contain any of %s' % [left, args].map(&:inspect)
49
+ context[:negation] ?
50
+ (contain && fail(failure % 'NOT')) :
51
+ (contain || fail(failure % ''))
52
+ end
53
+ alias_helper :contains_any?, :contain_any?
54
+ alias_helper :to_contain_any, :contain_any?
55
+
56
+ end
@@ -0,0 +1,108 @@
1
+ module Minispec
2
+
3
+ # a shortcut for `assert(something) == true`
4
+ # passes only if `subject == true`
5
+ #
6
+ # @example
7
+ # describe do
8
+ # let(:something) { true }
9
+ #
10
+ # test :something do
11
+ # is(something).true?
12
+ # end
13
+ # end
14
+ helper :true?, with_context: true do |left_object,context|
15
+ value, message = if left_object.is_a?(Proc)
16
+ returned = self.instance_exec(&left_object)
17
+ [
18
+ returned,
19
+ proc {'expected block at "%s" to return true, instead it returned %s' % [
20
+ MiniSpec::Utils.source(left_object),
21
+ returned.inspect
22
+ ]}
23
+ ]
24
+ else
25
+ [left_object, proc {'expected %s to be true' % left_object.inspect}]
26
+ end
27
+ context[:negation] ?
28
+ (value == true && fail('Not ' + message.call)) :
29
+ (value == true || fail(message.call.capitalize))
30
+ end
31
+ alias_helper :is_true, :true?
32
+
33
+ # passes if subject is not nil nor false
34
+ helper :positive, with_context: true do |left_object,context|
35
+ value, message = if left_object.is_a?(Proc)
36
+ returned = self.instance_exec(&left_object)
37
+ [
38
+ returned,
39
+ proc {'expected block at "%s" to return a non falsy value, instead it returned %s' % [
40
+ MiniSpec::Utils.source(left_object),
41
+ returned.inspect
42
+ ]}
43
+ ]
44
+ else
45
+ [
46
+ left_object,
47
+ proc{'expected %s to be non falsy' % left_object.inspect}
48
+ ]
49
+ end
50
+ context[:negation] ?
51
+ (value && fail('Not ' + message.call)) :
52
+ (value || fail(message.call.capitalize))
53
+ end
54
+ alias_helper :positive?, :positive
55
+ alias_helper :is_positive, :positive
56
+ alias_helper :truthful?, :positive
57
+ alias_helper :non_falsy?, :positive
58
+
59
+ # a shortcut for `assert(something) == false`
60
+ # passes only if `subject == false`
61
+ helper :false?, with_context: true do |left_object,context|
62
+ value, message = if left_object.is_a?(Proc)
63
+ returned = self.instance_exec(&left_object)
64
+ [
65
+ returned,
66
+ proc {'expected block at "%s" to return false, instead it returned %s' % [
67
+ MiniSpec::Utils.source(left_object),
68
+ returned.inspect
69
+ ]}
70
+ ]
71
+ else
72
+ [
73
+ left_object,
74
+ proc{'expected %s to be false' % left_object.inspect}
75
+ ]
76
+ end
77
+ context[:negation] ?
78
+ (value == false && fail('Not ' + message.call)) :
79
+ (value == false || fail(message.call.capitalize))
80
+ end
81
+ alias_helper :is_false, :false?
82
+
83
+ # passes if subject is nil or false
84
+ helper :negative, with_context: true do |left_object,context|
85
+ value, message = if left_object.is_a?(Proc)
86
+ returned = self.instance_exec(&left_object)
87
+ [
88
+ returned,
89
+ proc {'expected block at "%s" to return a falsy value, instead it returned %s' % [
90
+ MiniSpec::Utils.source(left_object),
91
+ returned.inspect
92
+ ]}
93
+ ]
94
+ else
95
+ [
96
+ left_object,
97
+ proc {'expected %s to be falsy' % left_object.inspect}
98
+ ]
99
+ end
100
+ context[:negation] ?
101
+ (value || fail('Not ' + message.call)) :
102
+ (value && fail(message.call.capitalize))
103
+ end
104
+ alias_helper :negative?, :negative
105
+ alias_helper :falsy?, :negative
106
+ alias_helper :is_falsy, :negative
107
+
108
+ end
@@ -0,0 +1,24 @@
1
+ module Minispec
2
+
3
+ # checks whether given block produces any output
4
+ #
5
+ # @example
6
+ # expect { ... }.to_be_silent
7
+ #
8
+ helper :silent, with_context: true do |block, context|
9
+ begin
10
+ $stdout, $stderr = StringIO.new, StringIO.new
11
+ self.instance_exec(&block)
12
+ message = 'Expected block at %s to be silent' % MiniSpec::Utils.source(block)
13
+ out = [$stdout, $stderr].map {|s| s.rewind; v = s.read; s.close; v}.join
14
+ context[:negation] ?
15
+ out.empty? && fail('Not %s' % message) :
16
+ out.empty? || fail(message + ". Instead it did output:\n" + out)
17
+ ensure
18
+ $stdout, $stderr = STDOUT, STDERR
19
+ end
20
+ end
21
+ alias_helper :silent?, :silent
22
+ alias_helper :is_silent, :silent
23
+ alias_helper :to_be_silent, :silent
24
+ end
@@ -0,0 +1,29 @@
1
+ module Minispec
2
+
3
+ # a helper to add expectations to objects.
4
+ # expectations will be validated when current test evaluation finished.
5
+ #
6
+ # @example
7
+ #
8
+ # expect(some_object).to_receive(:some_method)
9
+ #
10
+ # @example expecting multiple messages
11
+ #
12
+ # expect(some_object).to_receive(:some, :method)
13
+ #
14
+ helper :receive, with_context: true do |object,*args|
15
+ context = Hash[args.pop]
16
+ args.any? || raise(ArgumentError, 'Please provide at least one message')
17
+
18
+ args.size > 1 ?
19
+ context.update(expected_messages: args) :
20
+ context.update(expected_message: args.first)
21
+
22
+ expectation = MiniSpec::Mocks::Expectations.new(self, object, context, *args)
23
+ @__ms__expectations.push(expectation)
24
+ args.each {|m| proxy(object, m)}
25
+ expectation
26
+ end
27
+ alias_helper :receive?, :receive
28
+ alias_helper :to_receive, :receive
29
+ end
@@ -0,0 +1,36 @@
1
+ module Minispec
2
+
3
+ # ensure given object received expected message.
4
+ # expectations will be validated straight away
5
+ # so the object should be proxied before this helper used.
6
+ #
7
+ # @example spying on a explicitly "proxified" method
8
+ # spy(obj, :a)
9
+ # # ...
10
+ # assert(obj).received(:a)
11
+ #
12
+ # @example spying on a double
13
+ # user = double(:user, :new)
14
+ # # ...
15
+ # assert(user).received(:new)
16
+ #
17
+ # @note `received` helper works exactly as `to_receive` one,
18
+ # that's it, all validations available for expectations
19
+ # will work for spies too.
20
+ #
21
+ # @example ensure message received with specific arguments
22
+ # # ...
23
+ # assert(obj).received(:m).with(:x, :y)
24
+ #
25
+ # @example ensure specific value returned
26
+ # # ...
27
+ # assert(obj).received(:m).and_returned(:x)
28
+ #
29
+ helper :received, with_context: true do |object,*args|
30
+ context = Hash[args.pop]
31
+ args.any? || raise(ArgumentError, 'Please provide at least one message')
32
+ MiniSpec::Mocks::Validations.new(self, object, context, *args)
33
+ end
34
+ alias_helper :received?, :received
35
+
36
+ end
@@ -0,0 +1,44 @@
1
+ module Minispec
2
+
3
+ # checks whether given block raises an exception
4
+ #
5
+ # @example
6
+ # describe :Exceptions do
7
+ # let(:something_bad) { raise Something, 'bad' }
8
+ #
9
+ # should 'raise something bad' do
10
+ # does { something_bad }.raise? Something, 'bad'
11
+ # end
12
+ # end
13
+ #
14
+ helper :raise, with_context: true do |obj, *rest|
15
+ context = Hash[rest.pop]
16
+
17
+ # if a block passed to helper, it will be received as last but one argument,
18
+ # just before context, so popping it out cause we will consume it from context.
19
+ # normally we would pass no arguments if right block given,
20
+ # but we need to pass them cause arguments validation happens next in the stream
21
+ # and validator needs all data provided by user
22
+ context[:right_proc] && rest.pop
23
+
24
+ subject = begin
25
+ obj.is_a?(Proc) ? self.instance_exec(&obj) : obj
26
+ rescue Exception => e
27
+ e
28
+ end
29
+
30
+ result = MiniSpec::Utils.exception_raised?(subject, context, *rest, &context[:right_proc])
31
+ result.is_a?(MiniSpec::ExceptionError) && fail(result.message)
32
+ subject
33
+ end
34
+ alias_helper :raise?, :raise
35
+ alias_helper :raises, :raise
36
+ alias_helper :raises?, :raise
37
+ alias_helper :raise_error, :raise
38
+ alias_helper :raise_error?, :raise
39
+ alias_helper :raise_exception, :raise
40
+ alias_helper :raise_exception?, :raise
41
+ alias_helper :to_raise, :raise
42
+ alias_helper :to_raise_error, :raise
43
+ alias_helper :to_raise_exception, :raise
44
+ end
@@ -0,0 +1,29 @@
1
+ module Minispec
2
+
3
+ # checks whether given block throws a symbol
4
+ #
5
+ # @example
6
+ # does { something }.throw? :some_symbol
7
+ # does { something }.throw? :some_symbol, 'with some value'
8
+ # does { something }.throw? :some_symbol, /with some value/
9
+ helper :throw, with_context: true do |obj, *rest|
10
+ context = Hash[rest.pop]
11
+
12
+ # if a block passed to helper, it will be received as last but one argument,
13
+ # just before context, so popping it out cause we will consume it from context.
14
+ # normally we would pass no arguments if right block given,
15
+ # but we need to pass them cause arguments validation happens next in the stream
16
+ # and validator needs all data provided by user
17
+ context[:right_proc] && rest.pop
18
+
19
+ expected_symbol, expected_value = rest
20
+ obj.is_a?(Proc) || raise(ArgumentError, '`throw` helper works only with blocks')
21
+ status = MiniSpec::Utils.symbol_thrown?(expected_symbol, expected_value, context, &obj)
22
+ status.is_a?(MiniSpec::ThrowError) && fail(status.message)
23
+ end
24
+ alias_helper :throw?, :throw
25
+ alias_helper :throw_symbol, :throw
26
+ alias_helper :throw_symbol?, :throw
27
+ alias_helper :to_throw, :throw
28
+ alias_helper :to_throw_symbol, :throw
29
+ end
@@ -0,0 +1,11 @@
1
+ module MiniSpec
2
+ module Mocks
3
+ class HashedStub
4
+ def self.with(*)
5
+ raise(ArgumentError, "`with' can not be used on stubs/mocks defined using a Hash")
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ Dir[File.expand_path('../mocks/*.rb', __FILE__)].each {|f| require(f)}
@@ -0,0 +1,77 @@
1
+ module MiniSpec
2
+ module Mocks
3
+ class Expectations
4
+
5
+ def initialize base, object, context, *args
6
+ @base, @object, @context, @args = base, object, context, args
7
+ @expectations = []
8
+ end
9
+
10
+ def validate!
11
+ validator = Validations.new(@base, @object, @context, *@args)
12
+ @expectations.each {|e| e.call(validator)}
13
+ end
14
+
15
+ def with *args, &proc
16
+ push {|v| proc ? v.with(&proc) : v.with(*args)}
17
+ end
18
+
19
+ def without_arguments
20
+ push {|v| v.without_arguments}
21
+ end
22
+ alias without_any_arguments without_arguments
23
+
24
+ def with_caller *args, &proc
25
+ push {|v| proc ? v.with_caller(&proc) : v.with_caller(*args)}
26
+ end
27
+
28
+ def and_return *args, &proc
29
+ push {|v| proc ? v.and_return(&proc) : v.and_return(*args)}
30
+ end
31
+ alias and_returned and_return
32
+
33
+ def and_yield *args, &proc
34
+ push {|v| proc ? v.and_yield(&proc) : v.and_yield(*args)}
35
+ end
36
+
37
+ def without_yield
38
+ push {|v| v.without_yield}
39
+ end
40
+
41
+ def and_raise *args, &proc
42
+ push {|v| v.and_raise(*args, &proc)}
43
+ end
44
+
45
+ def without_raise
46
+ push {|v| v.without_raise}
47
+ end
48
+
49
+ def and_throw *args, &proc
50
+ push {|v| v.and_throw(*args, &proc)}
51
+ end
52
+
53
+ def without_throw
54
+ push {|v| v.without_throw}
55
+ end
56
+
57
+ def count *expected, &proc
58
+ push {|v| proc ? v.count(&proc) : v.count(*expected)}
59
+ end
60
+ alias times count
61
+
62
+ def once; count(1); end
63
+ def twice; count(2); end
64
+
65
+ def ordered n = 1
66
+ push {|v| v.ordered(n)}
67
+ end
68
+
69
+ private
70
+ def push &proc
71
+ @expectations << proc
72
+ self
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,178 @@
1
+ module MiniSpec
2
+ module Mocks
3
+ class MultipleStubsProxy
4
+ def initialize stubs
5
+ @stubs = stubs.freeze
6
+ end
7
+
8
+ def with *a, &b
9
+ @stubs.each {|s| s.with(*a, &b)}
10
+ self
11
+ end
12
+
13
+ def with_any *a, &b
14
+ @stubs.each {|s| s.with_any(*a, &b)}
15
+ self
16
+ end
17
+ end
18
+
19
+ class Stub
20
+
21
+ def initialize object, messages, stubs = {}
22
+ @object, @messages, @stubs, @blocks = object, messages, stubs, {}
23
+ end
24
+
25
+ # makes a stub to behave differently depending on given arguments
26
+ #
27
+ # @example
28
+ # stub(a, :b).
29
+ # with(1) { :one }. # `a.b(1)` will return :one
30
+ # with(2) { :two } # `a.b(2)` will return :two
31
+ #
32
+ def with *args, &block
33
+ @blocks[args] = block || proc {}
34
+ self
35
+ end
36
+
37
+ # defines a catchall block
38
+ #
39
+ # @example with a block
40
+ # stub(a, :b).
41
+ # with(1) { :one }. # `a.b(1)` will return :one
42
+ # with_any { :any } # `a.b(:any_args, :or_no_args)` will return :any
43
+ #
44
+ # @example with a value
45
+ # stub(a, :b).
46
+ # with(1) { :one }.
47
+ # with_any(:any)
48
+ #
49
+ def with_any *args, &block
50
+ # this args mangling needed to be able to accept nil or false as value
51
+ (value_given = args.size == 1) || args.size == 0 ||
52
+ raise(ArgumentError, 'Wrong number of arguments, %i instead of 0..1' % args.size)
53
+ value_given && block && raise(ArgumentError, 'Both a value and a block used. Please use either one')
54
+ value_given || block || raise(ArgumentError, 'No value nor block provided')
55
+ @catchall_block = value_given ? proc { args.first } : block
56
+ self
57
+ end
58
+ alias any with_any
59
+
60
+ # defines given stub of given visibility on `@object`
61
+ #
62
+ # @param stub
63
+ # - when stub is a symbol it defines a method.
64
+ # if `@object` has a singleton method with stub name or the stub name is :nil?,
65
+ # the method are defined using `define_singleton_method`.
66
+ # otherwise `@object` are extended with a module containing needed methods(@see #mixin)
67
+ # - when given stub contain dots, a chained stub defined(@see #define_chained_stub)
68
+ #
69
+ # @param visibility stub visibility, :public(default), :protected, :private
70
+ # @param [Proc] &proc block to be yielded when stub called
71
+ # @raise ArgumentError if given stub is not a Symbol, String, Hash or Array
72
+ def stubify stub, visibility, &block
73
+ stub.is_a?(Symbol) || raise('stubs names should be provided as symbols')
74
+
75
+ @catchall_block = block if block # IMPORTANT! update catchall block only if another block given
76
+
77
+ @object.singleton_methods.include?(stub) || stub == :nil? ?
78
+ define_singleton_stub(stub) :
79
+ define_regular_stub(stub, visibility)
80
+ end
81
+
82
+ def most_relevant_block_for args
83
+ @blocks[args] || @catchall_block || proc {}
84
+ end
85
+
86
+ private
87
+ def define_regular_stub stub, visibility
88
+ @object.extend(mixin(stub, visibility))
89
+ # defining a singleton if stub can not be inserted via mixin
90
+ define_singleton_stub(stub) unless MiniSpec::Utils.method_defined?(@object, stub)
91
+ end
92
+
93
+ def define_singleton_stub stub
94
+ @object.define_singleton_method(stub, &proxy(stub))
95
+ end
96
+
97
+ # defines a new Module that contains given stub method of given visibility
98
+ #
99
+ # @param stub method to be defined
100
+ # @param visibility method visibility. default - public
101
+ # @param &block block to be yielded when method called
102
+ # @return [Module]
103
+ def mixin stub, visibility = nil
104
+ method = proxy(stub)
105
+ Module.new do
106
+ define_method(stub, &method)
107
+ protected stub.to_sym if visibility == :protected
108
+ private stub.to_sym if visibility == :private
109
+ end
110
+ end
111
+
112
+ # builds a block that will be yielded when given method called.
113
+ # when yielded, the block will collect received messages stat.
114
+ #
115
+ # the block will receive original method as first argument
116
+ # and any passed params as rest arguments.
117
+ #
118
+ # @param method_name
119
+ # @param &block
120
+ # @return [Proc]
121
+ def proxy method_name
122
+ method_name.is_a?(Symbol) || raise(ArgumentError, 'method name should be a Symbol')
123
+ register_stub(method_name)
124
+ base, object, messages, original = self, @object, @messages, original(method_name)
125
+
126
+ Proc.new do |*args,&block|
127
+ message = {
128
+ object: object,
129
+ method: method_name,
130
+ arguments: args,
131
+ caller: Array(caller)
132
+ }
133
+ messages.push(message)
134
+
135
+ method = base.most_relevant_block_for(args)
136
+
137
+ proc = block ? Proc.new do |*a,&b|
138
+ message[:yielded] = a
139
+ block.call(*a, &b)
140
+ end : nil
141
+
142
+ begin
143
+ message[:returned] = method.call(original, *args, &proc)
144
+ rescue Exception => e
145
+ message[:raised] = e
146
+ end
147
+ message.freeze
148
+ message[:raised] ? raise(message[:raised]) : message[:returned]
149
+ end
150
+ end
151
+
152
+ # tracking what methods was stubbed on what objects
153
+ # so they can be unstubbed when current test evaluation finished.
154
+ #
155
+ # @param stub method to be stubbed
156
+ def register_stub stub
157
+ stub.is_a?(Symbol) || raise(ArgumentError, 'stub should be a symbol')
158
+
159
+ return if originals.has_key?(stub)
160
+ if stub == :nil? || @object.singleton_methods.include?(stub)
161
+ return originals.update(stub => [@object.method(stub), :singleton])
162
+ end
163
+ if visibility = MiniSpec::Utils.method_visibility(@object, stub)
164
+ return originals.update(stub => [@object.method(stub), visibility])
165
+ end
166
+ originals.update(stub => [])
167
+ end
168
+
169
+ def originals
170
+ @stubs[@object] ||= {}
171
+ end
172
+
173
+ def original method
174
+ originals[method] && originals[method].first
175
+ end
176
+ end
177
+ end
178
+ end