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,80 @@
|
|
1
|
+
module MiniSpec
|
2
|
+
module Mocks
|
3
|
+
class Validations
|
4
|
+
include MiniSpec::Utils
|
5
|
+
|
6
|
+
def initialize base, object, context, *expected_messages
|
7
|
+
expected_messages.empty? && raise(ArgumentError, 'Wrong number of arguments (3 for 4+)')
|
8
|
+
expected_messages.all? {|m| m.is_a?(Symbol)} || raise(ArgumentError, 'Only symbols accepted')
|
9
|
+
@base, @object, @context, @failed = base, object, context, false
|
10
|
+
@expected_messages = expected_messages.freeze
|
11
|
+
@messages = expected_and_received.freeze
|
12
|
+
validate_received_messages!
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
# selecting only expected messages in the order they was received.
|
17
|
+
# @param expected_messages [Array]
|
18
|
+
# @return [Hash]
|
19
|
+
def expected_and_received
|
20
|
+
@base.__ms__mocks__instance_messages(@context[:left_object]).inject({}) do |map,msg|
|
21
|
+
@expected_messages.include?(msg[:method]) && (map[msg[:method]] ||= []).push(msg)
|
22
|
+
map
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_received_messages!
|
27
|
+
@expected_messages.each do |m|
|
28
|
+
@context[:negation] ?
|
29
|
+
@messages.keys.include?(m) && message_validation_error!(m, true) :
|
30
|
+
@messages.keys.include?(m) || message_validation_error!(m)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def message_validation_error! message, negation = false
|
35
|
+
fail_with('%sExpected %s to receive %s message' % [
|
36
|
+
negation ? 'NOT ' : '',
|
37
|
+
pp(@object),
|
38
|
+
pp(message),
|
39
|
+
])
|
40
|
+
end
|
41
|
+
|
42
|
+
def single_message_expected?
|
43
|
+
@expected_messages.size == 1
|
44
|
+
end
|
45
|
+
|
46
|
+
# checks whether correct number of arguments given.
|
47
|
+
# in any situation, at least one argument required.
|
48
|
+
# if multiple messages expected, number of arguments should be equal to one
|
49
|
+
# or to the number of expected messages.
|
50
|
+
def assert_given_arguments_match_received_messages *args, &block
|
51
|
+
if block
|
52
|
+
args.empty? || raise(ArgumentError, 'Both arguments and block given. Please use either one.')
|
53
|
+
return true # if block given, no arguments accepted, so nothing to validate
|
54
|
+
end
|
55
|
+
|
56
|
+
# single argument acceptable for any number of expected messages
|
57
|
+
return if args.size == 1
|
58
|
+
|
59
|
+
# when a single message expected, any number of arguments accepted
|
60
|
+
return if @expected_messages.size == 1
|
61
|
+
|
62
|
+
# on multiple messages, number of arguments should match number of expected messages
|
63
|
+
return if args.size == @expected_messages.size
|
64
|
+
|
65
|
+
raise(ArgumentError, 'wrong number of arguments (%i for 1..%i)' % [
|
66
|
+
args.size,
|
67
|
+
@expected_messages.size
|
68
|
+
], caller[1..-1])
|
69
|
+
end
|
70
|
+
|
71
|
+
def fail_with message
|
72
|
+
return unless @failed = message
|
73
|
+
@base.fail(message)
|
74
|
+
self
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
Dir[File.expand_path('../validations/**/*.rb', __FILE__)].each {|f| require(f)}
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class MiniSpec::Mocks::Validations
|
2
|
+
|
3
|
+
# assure expected message(s) was received a specific amount of times
|
4
|
+
#
|
5
|
+
# @example expect `a` to be received exactly 2 times
|
6
|
+
# expect(obj).to_receive(:a).count(2)
|
7
|
+
#
|
8
|
+
# @example expect `a` to be received 2 or more times
|
9
|
+
# expect(obj).to_receive(:a).count {|a| a >= 2}
|
10
|
+
#
|
11
|
+
# @example expect `a` and `b` to be received 2 times each
|
12
|
+
# expect(obj).to_receive(:a, :b).count(2)
|
13
|
+
#
|
14
|
+
# @example expect `a` to be received 2 times and `b` 3 times
|
15
|
+
# expect(obj).to_receive(:a, :b).count(2, 3)
|
16
|
+
#
|
17
|
+
# @example expect both `a` and `b` to be received more than 2 times
|
18
|
+
# expect(obj).to_receive(:a, :b).count {|a,b| a > 2 && b > 2}
|
19
|
+
#
|
20
|
+
def count *expected, &block
|
21
|
+
return self if @failed
|
22
|
+
assert_given_arguments_match_received_messages(*expected, &block)
|
23
|
+
received = received_amounts
|
24
|
+
|
25
|
+
if block
|
26
|
+
return @base.instance_exec(*received.values, &block) ||
|
27
|
+
amount_error!(@expected_messages, block, received)
|
28
|
+
end
|
29
|
+
|
30
|
+
expected = zipper(@expected_messages, expected)
|
31
|
+
received.each_pair do |message,amount|
|
32
|
+
# each message should be received expected amount of times
|
33
|
+
amount == expected[message] ||
|
34
|
+
amount_error!(message, expected[message], amount)
|
35
|
+
end
|
36
|
+
self
|
37
|
+
end
|
38
|
+
alias times count
|
39
|
+
|
40
|
+
def once; count(1); end
|
41
|
+
def twice; count(2); end
|
42
|
+
|
43
|
+
private
|
44
|
+
# returns a Hash of messages each with amount of times it was called.
|
45
|
+
# basically it does the same as `@messages.values.map(&:size)`
|
46
|
+
# except it returns the messages in the order they are expected.
|
47
|
+
def received_amounts
|
48
|
+
@expected_messages.inject({}) do |map,msg|
|
49
|
+
map.merge(msg => (@messages[msg] || []).size)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def amount_error! messages, expected, received
|
54
|
+
fail_with("%s received %s message(s) wrong amount of times.\nExpected: %s\nActual: %s" % [
|
55
|
+
pp(@object),
|
56
|
+
pp(messages),
|
57
|
+
expected.is_a?(Proc) ?
|
58
|
+
('to be validated at %s' % pp(source(expected))) :
|
59
|
+
Array(expected).map {|x| pp(x)}*', ',
|
60
|
+
pp(received)
|
61
|
+
])
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
class MiniSpec::Mocks::Validations
|
2
|
+
|
3
|
+
# validates received arguments against expected ones
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# expect(obj).to_receive(:a).with(1)
|
7
|
+
# obj.a(1)
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# expect(obj).to_receive(:a).with(1, 2)
|
11
|
+
# obj.a(1, 2)
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# expect(obj).to_receive(:a).with(1, [:a, :b, :c])
|
15
|
+
# obj.a(1, [:a, :b, :c])
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# expect(obj).to_receive(:a).with {|x| x[0] == [1, 2, 3] && x[1] == [:x, [:y], 'z']}
|
19
|
+
# obj.a(1, 2, 3)
|
20
|
+
# obj.a(:x, [:y], 'z')
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# expect(obj).to_receive(:a, :b, :c).with(1)
|
24
|
+
# obj.a(1)
|
25
|
+
# obj.b(1)
|
26
|
+
# obj.c(1)
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# expect(obj).to_receive(:a, :b, :c).with(1, 2, 3)
|
30
|
+
# obj.a(1)
|
31
|
+
# obj.b(2)
|
32
|
+
# obj.c(3)
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# expect(obj).to_receive(:a, :b, :c).with([1, 2, 3])
|
36
|
+
# obj.a(1, 2, 3)
|
37
|
+
# obj.b(1, 2, 3)
|
38
|
+
# obj.c(1, 2, 3)
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# expect(obj).to_receive(:a, :b, :c).with([1, 2], [:x, :y], :z)
|
42
|
+
# obj.a(1, 2)
|
43
|
+
# obj.b(:x, :y)
|
44
|
+
# obj.c(:z)
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# expect(obj).to_receive(:a, :b, :c).with([[1, 2]], [[:x, :y]], [:z])
|
48
|
+
# obj.a([1, 2])
|
49
|
+
# obj.b([:x, :y])
|
50
|
+
# obj.c([:z])
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# expect(obj).to_receive(:a, :b, :c).with do |a,b,c|
|
54
|
+
# a == [[1, 2]] &&
|
55
|
+
# b == [[:x, :y]] &&
|
56
|
+
# c == [:z]
|
57
|
+
# end
|
58
|
+
# obj.a(1, 2)
|
59
|
+
# obj.b(:x, :y)
|
60
|
+
# obj.c(:z)
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# expect(obj).to_receive(:a, :b, :c).with do |a,b,c|
|
64
|
+
# a == [[1, 2], [3, 4]] &&
|
65
|
+
# b == [[:x, :y], [2]] &&
|
66
|
+
# c == [[:z], [[:a, :b], :c]]
|
67
|
+
# end
|
68
|
+
# obj.a(1, 2)
|
69
|
+
# obj.a(3, 4)
|
70
|
+
# obj.b(:x, :y)
|
71
|
+
# obj.b(2)
|
72
|
+
# obj.c(:z)
|
73
|
+
# obj.c([:a, :b], :c)
|
74
|
+
#
|
75
|
+
def with *expected, &block
|
76
|
+
return self if @failed
|
77
|
+
assert_given_arguments_match_received_messages(*expected, &block)
|
78
|
+
received = received_arguments
|
79
|
+
|
80
|
+
if block
|
81
|
+
return @base.instance_exec(*received.values, &block) ||
|
82
|
+
arguments_error!(@expected_messages, block, received)
|
83
|
+
end
|
84
|
+
|
85
|
+
single_message_expected? ?
|
86
|
+
validate_arguments(expected, received) :
|
87
|
+
validate_arguments_list(expected, received)
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
def without_arguments
|
92
|
+
return self if @failed
|
93
|
+
received_arguments.each_pair do |msg,args|
|
94
|
+
# each message should be called without arguments at least once
|
95
|
+
args.any?(&:empty?) || arguments_error!(msg, [], msg => args)
|
96
|
+
end
|
97
|
+
self
|
98
|
+
end
|
99
|
+
alias without_any_arguments without_arguments
|
100
|
+
|
101
|
+
private
|
102
|
+
# returns a Hash of received messages,
|
103
|
+
# each with a list of arguments it was called with.
|
104
|
+
#
|
105
|
+
# @example
|
106
|
+
# obj.a(:x)
|
107
|
+
# obj.a([:x])
|
108
|
+
# obj.a(:y, [:z])
|
109
|
+
# => { a: [ [:x], [[:x]], [:y, [:z]] ] }
|
110
|
+
#
|
111
|
+
def received_arguments
|
112
|
+
@expected_messages.inject({}) do |map,msg|
|
113
|
+
map.merge(msg => @messages[msg] ? @messages[msg].map {|m| m[:arguments]} : [])
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def validate_arguments expected, received
|
118
|
+
message = @expected_messages[0]
|
119
|
+
arguments = received[message]
|
120
|
+
return if arguments.any? {|x| x == expected}
|
121
|
+
arguments_error!(message, expected, message => arguments)
|
122
|
+
end
|
123
|
+
|
124
|
+
def validate_arguments_list expected, received
|
125
|
+
expected = zipper(@expected_messages, expected)
|
126
|
+
received.each_pair do |msg,args|
|
127
|
+
next if args.any? {|x| x == [expected[msg]]}
|
128
|
+
arguments_error!(msg, expected[msg], msg => args)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def stringify_expected_arguments expected
|
133
|
+
return 'to be validated at %s' % pp(source(expected)) if expected.is_a?(Proc)
|
134
|
+
expected = Array(expected)
|
135
|
+
return 'to be called without arguments' if expected.empty?
|
136
|
+
expected.map {|a| pp(a)}*', '
|
137
|
+
end
|
138
|
+
|
139
|
+
def stringify_received_arguments received
|
140
|
+
received.is_a?(Hash) || raise(ArgumentError, 'expected a Hash')
|
141
|
+
received.map do |msg,args|
|
142
|
+
'%s called %s' % [
|
143
|
+
pp(msg),
|
144
|
+
args.map do |arr|
|
145
|
+
arr.empty? ?
|
146
|
+
'without arguments' :
|
147
|
+
'with %s' % arr.map {|a| pp(a)}.join(', ')
|
148
|
+
end*' then '
|
149
|
+
]
|
150
|
+
end*"\n "
|
151
|
+
end
|
152
|
+
|
153
|
+
def arguments_error! message, expected, received
|
154
|
+
fail_with("%s received %s message(s) with unexpected arguments.\nExpected: %s\nActual: %s" % [
|
155
|
+
pp(@object),
|
156
|
+
pp(message),
|
157
|
+
stringify_expected_arguments(expected),
|
158
|
+
stringify_received_arguments(received)
|
159
|
+
])
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class MiniSpec::Mocks::Validations
|
2
|
+
|
3
|
+
def with_caller *expected, &block
|
4
|
+
return self if @failed
|
5
|
+
assert_given_arguments_match_received_messages(*expected, &block)
|
6
|
+
received = received_callers
|
7
|
+
|
8
|
+
if block
|
9
|
+
return @base.instance_exec(*received.values, &block) ||
|
10
|
+
caller_error!(@expected_messages, block)
|
11
|
+
end
|
12
|
+
|
13
|
+
expected = zipper(@expected_messages, expected)
|
14
|
+
received.each_pair do |msg,callers|
|
15
|
+
# each message should be called from expected caller at least once
|
16
|
+
callers.any? {|line| caller_match?(line, expected[msg])} ||
|
17
|
+
caller_error!(msg, expected[msg])
|
18
|
+
end
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def received_callers
|
24
|
+
@expected_messages.inject({}) do |map,msg|
|
25
|
+
map.merge(msg => @messages[msg] ? @messages[msg].map {|m| m[:caller]} : [])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def caller_match? line, pattern
|
30
|
+
regexp = pattern.is_a?(Regexp) ? pattern : Regexp.new(Regexp.escape(pattern))
|
31
|
+
line.any? {|l| l =~ regexp}
|
32
|
+
end
|
33
|
+
|
34
|
+
def caller_error! message, expected
|
35
|
+
fail_with("%s received %s message(s) from wrong location.\nCaller does not %s" % [
|
36
|
+
pp(@object),
|
37
|
+
pp(message),
|
38
|
+
expected.is_a?(Proc) ?
|
39
|
+
('pass validation at %s' % pp(source(expected))) :
|
40
|
+
('match %s' % pp(expected))
|
41
|
+
])
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class MiniSpec::Mocks::Validations
|
2
|
+
|
3
|
+
# checks whether expected messages was received in a specific order
|
4
|
+
#
|
5
|
+
# @note this method will work only when multiple messages expected.
|
6
|
+
# that's it, unlike RSpec, it wont work like this:
|
7
|
+
# `expect(obj).to_receive(:a).ordered`
|
8
|
+
# `expect(obj).to_receive(:b).ordered`
|
9
|
+
#
|
10
|
+
# instead it will work like this:
|
11
|
+
# `expect(obj).to_receive(:a, :b).ordered`
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# expect(obj).to_receive(:a, :b, :c).ordered
|
15
|
+
#
|
16
|
+
# @example expect for same sequence N times
|
17
|
+
# expect(obj).to_receive(:a, :b).ordered(2)
|
18
|
+
# # for this to pass `obj.a` and `obj.b` should be both called twice in same order
|
19
|
+
#
|
20
|
+
def ordered n = 1, &block
|
21
|
+
block && raise(ArgumentError, '#ordered does not accept a block')
|
22
|
+
n.is_a?(Integer) || raise(ArgumentError, '#ordered expects a single Integer argument')
|
23
|
+
single_message_expected? && raise(ArgumentError, '#ordered works only with multiple messages')
|
24
|
+
received_in_expected_order?(n)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def received_in_expected_order? n
|
29
|
+
x = 0
|
30
|
+
messages_in_received_order.each_cons(@expected_messages.size) {|c| x += 1 if c == @expected_messages}
|
31
|
+
x == n || ordered_error!(n, x)
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns an Array of all messages in the order they was received
|
35
|
+
def messages_in_received_order
|
36
|
+
@base.__ms__mocks__instance_messages(@context[:left_object]).map {|m| m[:method]}
|
37
|
+
end
|
38
|
+
|
39
|
+
def ordered_error! expected, received
|
40
|
+
fail_with("Expected %s to receive %s sequence %s times.\nInstead it was received %s times." % [
|
41
|
+
pp(@object),
|
42
|
+
pp(@expected_messages),
|
43
|
+
expected,
|
44
|
+
received
|
45
|
+
])
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
class MiniSpec::Mocks::Validations
|
2
|
+
|
3
|
+
# expect received message(s) to raise a exception.
|
4
|
+
#
|
5
|
+
# if no args given any raised exception accepted.
|
6
|
+
# if a class given it checks whether raised exception is of given type.
|
7
|
+
# if a string or regexp given it checks whether raised message matches it.
|
8
|
+
#
|
9
|
+
# @example expect `a` to raise something
|
10
|
+
# expect(obj).to_receive(:a).and_raise
|
11
|
+
#
|
12
|
+
# @example expect `a` to raise ArgumentError
|
13
|
+
# expect(obj).to_receive(:a).and_raise(ArgumentError)
|
14
|
+
#
|
15
|
+
# @example raised exception should be of ArgumentError type and match /something/
|
16
|
+
# expect(obj).to_receive(:a).and_raise([ArgumentError, /something/])
|
17
|
+
#
|
18
|
+
# @example expect `a` to raise ArgumentError and `b` to raise RuntimeError
|
19
|
+
# expect(obj).to_receive(:a, :b).and_raise(ArgumentError, RuntimeError)
|
20
|
+
#
|
21
|
+
# @example expect `a` to raise ArgumentError matching /something/ and `b` to raise RuntimeError
|
22
|
+
# expect(obj).to_receive(:a, :b).and_raise([ArgumentError, /something/], RuntimeError)
|
23
|
+
#
|
24
|
+
def and_raise *expected, &block
|
25
|
+
return self if @failed
|
26
|
+
# `and_raise` can be called without arguments
|
27
|
+
expected.empty? || assert_given_arguments_match_received_messages(*expected, &block)
|
28
|
+
received = raised_exceptions
|
29
|
+
|
30
|
+
if block
|
31
|
+
return @base.instance_exec(*received.values, &block) ||
|
32
|
+
exception_error!(@expected_messages, block, received)
|
33
|
+
end
|
34
|
+
|
35
|
+
expected = single_message_expected? ?
|
36
|
+
{@expected_messages[0] => expected} :
|
37
|
+
zipper(@expected_messages, expected)
|
38
|
+
context = @context.merge(negation: nil, right_proc: nil) # do NOT alter @context
|
39
|
+
received.each_pair do |msg,calls|
|
40
|
+
# each message should raise as expected at least once
|
41
|
+
calls.any? {|c| exception_raised?(c, context, *expected[msg]) == true} ||
|
42
|
+
exception_error!(msg, expected[msg], msg => calls)
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
alias and_raised and_raise
|
47
|
+
alias and_raised? and_raise
|
48
|
+
|
49
|
+
# make sure received message(s) does not raise any exception
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# expect(obj).to_receive(:a).without_raise
|
53
|
+
#
|
54
|
+
def without_raise
|
55
|
+
return self if @failed
|
56
|
+
raised_exceptions.each_pair do |msg,calls|
|
57
|
+
calls.any? {|r| r.is_a?(Exception)} && unexpected_exception_error!(msg, calls)
|
58
|
+
end
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def raised_exceptions
|
64
|
+
@expected_messages.inject({}) do |map,msg|
|
65
|
+
map.merge(msg => @messages[msg] ? @messages[msg].map {|m| m[:raised]} : [])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def unexpected_exception_error! message, received
|
70
|
+
fail_with("%s received %s message and raised an unexpected error.\nExpected: %s\nActual: %s" % [
|
71
|
+
pp(@object),
|
72
|
+
pp(message),
|
73
|
+
'nothing to be raised',
|
74
|
+
stringify_received_exception(message => received)
|
75
|
+
])
|
76
|
+
end
|
77
|
+
|
78
|
+
def exception_error! message, expected, received
|
79
|
+
fail_with("%s received %s message(s) but did not raise accordingly.\nExpected: %s\nActual: %s" % [
|
80
|
+
pp(@object),
|
81
|
+
pp(message),
|
82
|
+
stringify_expected_exception(expected),
|
83
|
+
stringify_received_exception(received)
|
84
|
+
])
|
85
|
+
end
|
86
|
+
|
87
|
+
def stringify_expected_exception expected
|
88
|
+
return 'any exception to be raised' unless expected
|
89
|
+
return 'raised exception to be validated at %s' % pp(source(expected)) if expected.is_a?(Proc)
|
90
|
+
Array(expected).map(&method(:pp))*':'
|
91
|
+
end
|
92
|
+
|
93
|
+
def stringify_received_exception received
|
94
|
+
received.is_a?(Hash) || raise(ArgumentError, 'a Hash expected')
|
95
|
+
received.map do |msg,calls|
|
96
|
+
calls.each_with_index.map do |call,i|
|
97
|
+
'%s call #%s raised %s' % [
|
98
|
+
pp(msg),
|
99
|
+
i + 1,
|
100
|
+
call.is_a?(Exception) ?
|
101
|
+
stringify_exception(call) :
|
102
|
+
'nothing'
|
103
|
+
]
|
104
|
+
end*"\n "
|
105
|
+
end*"\n "
|
106
|
+
end
|
107
|
+
|
108
|
+
def stringify_exception exception
|
109
|
+
[exception.class, exception.message].map(&method(:pp))*':'
|
110
|
+
end
|
111
|
+
end
|