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