minispec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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