minispec 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.pryrc +2 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +2140 -0
- data/Rakefile +11 -0
- data/bin/minispec +4 -0
- data/lib/minispec.rb +175 -0
- data/lib/minispec/api.rb +2 -0
- data/lib/minispec/api/class.rb +195 -0
- data/lib/minispec/api/class/after.rb +49 -0
- data/lib/minispec/api/class/around.rb +54 -0
- data/lib/minispec/api/class/before.rb +101 -0
- data/lib/minispec/api/class/helpers.rb +116 -0
- data/lib/minispec/api/class/let.rb +44 -0
- data/lib/minispec/api/class/tests.rb +33 -0
- data/lib/minispec/api/instance.rb +158 -0
- data/lib/minispec/api/instance/mocks/doubles.rb +36 -0
- data/lib/minispec/api/instance/mocks/mocks.rb +319 -0
- data/lib/minispec/api/instance/mocks/spies.rb +17 -0
- data/lib/minispec/api/instance/mocks/stubs.rb +105 -0
- data/lib/minispec/helpers.rb +1 -0
- data/lib/minispec/helpers/array.rb +56 -0
- data/lib/minispec/helpers/booleans.rb +108 -0
- data/lib/minispec/helpers/generic.rb +24 -0
- data/lib/minispec/helpers/mocks/expectations.rb +29 -0
- data/lib/minispec/helpers/mocks/spies.rb +36 -0
- data/lib/minispec/helpers/raise.rb +44 -0
- data/lib/minispec/helpers/throw.rb +29 -0
- data/lib/minispec/mocks.rb +11 -0
- data/lib/minispec/mocks/expectations.rb +77 -0
- data/lib/minispec/mocks/stubs.rb +178 -0
- data/lib/minispec/mocks/validations.rb +80 -0
- data/lib/minispec/mocks/validations/amount.rb +63 -0
- data/lib/minispec/mocks/validations/arguments.rb +161 -0
- data/lib/minispec/mocks/validations/caller.rb +43 -0
- data/lib/minispec/mocks/validations/order.rb +47 -0
- data/lib/minispec/mocks/validations/raise.rb +111 -0
- data/lib/minispec/mocks/validations/return.rb +74 -0
- data/lib/minispec/mocks/validations/throw.rb +91 -0
- data/lib/minispec/mocks/validations/yield.rb +141 -0
- data/lib/minispec/proxy.rb +201 -0
- data/lib/minispec/reporter.rb +185 -0
- data/lib/minispec/utils.rb +139 -0
- data/lib/minispec/utils/differ.rb +325 -0
- data/lib/minispec/utils/pretty_print.rb +51 -0
- data/lib/minispec/utils/raise.rb +123 -0
- data/lib/minispec/utils/throw.rb +140 -0
- data/minispec.gemspec +27 -0
- data/test/mocks/expectations/amount.rb +67 -0
- data/test/mocks/expectations/arguments.rb +126 -0
- data/test/mocks/expectations/caller.rb +55 -0
- data/test/mocks/expectations/generic.rb +35 -0
- data/test/mocks/expectations/order.rb +46 -0
- data/test/mocks/expectations/raise.rb +166 -0
- data/test/mocks/expectations/return.rb +71 -0
- data/test/mocks/expectations/throw.rb +113 -0
- data/test/mocks/expectations/yield.rb +109 -0
- data/test/mocks/spies/amount.rb +68 -0
- data/test/mocks/spies/arguments.rb +57 -0
- data/test/mocks/spies/generic.rb +61 -0
- data/test/mocks/spies/order.rb +38 -0
- data/test/mocks/spies/raise.rb +158 -0
- data/test/mocks/spies/return.rb +71 -0
- data/test/mocks/spies/throw.rb +113 -0
- data/test/mocks/spies/yield.rb +101 -0
- data/test/mocks/test__doubles.rb +98 -0
- data/test/mocks/test__expectations.rb +27 -0
- data/test/mocks/test__mocks.rb +197 -0
- data/test/mocks/test__proxies.rb +61 -0
- data/test/mocks/test__spies.rb +43 -0
- data/test/mocks/test__stubs.rb +427 -0
- data/test/proxified_asserts.rb +34 -0
- data/test/setup.rb +53 -0
- data/test/test__around.rb +58 -0
- data/test/test__assert.rb +510 -0
- data/test/test__before_and_after.rb +117 -0
- data/test/test__before_and_after_all.rb +71 -0
- data/test/test__helpers.rb +197 -0
- data/test/test__raise.rb +104 -0
- data/test/test__skip.rb +41 -0
- data/test/test__throw.rb +103 -0
- 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,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
|