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