muack 1.2.0 → 1.5.0
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 +5 -5
- data/.travis.yml +19 -9
- data/CHANGES.md +47 -0
- data/Gemfile +0 -4
- data/README.md +162 -65
- data/Rakefile +3 -4
- data/lib/muack.rb +43 -11
- data/lib/muack/block.rb +4 -15
- data/lib/muack/block_26.rb +16 -0
- data/lib/muack/block_27.rb +16 -0
- data/lib/muack/coat.rb +1 -1
- data/lib/muack/definition.rb +4 -3
- data/lib/muack/error.rb +6 -0
- data/lib/muack/failure.rb +5 -4
- data/lib/muack/mock.rb +134 -68
- data/lib/muack/satisfying.rb +195 -0
- data/lib/muack/spy.rb +18 -4
- data/lib/muack/stub.rb +7 -6
- data/lib/muack/test.rb +42 -11
- data/lib/muack/version.rb +1 -1
- data/muack.gemspec +65 -55
- data/task/README.md +8 -8
- data/task/gemgem.rb +34 -7
- data/test/test_any_instance_of.rb +16 -2
- data/test/test_from_readme.rb +6 -8
- data/test/test_keyargs.rb +111 -0
- data/test/test_modifier.rb +6 -6
- data/test/test_prepend.rb +121 -0
- data/test/test_proxy.rb +19 -4
- data/test/{test_satisfy.rb → test_satisfying.rb} +216 -80
- data/test/test_spy.rb +149 -0
- data/test/test_stub.rb +0 -95
- data/test/test_visibility.rb +120 -0
- metadata +20 -11
- data/lib/muack/satisfy.rb +0 -100
data/Rakefile
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
|
2
2
|
begin
|
3
|
-
require "#{
|
3
|
+
require "#{__dir__}/task/gemgem"
|
4
4
|
rescue LoadError
|
5
|
-
sh 'git submodule update --init'
|
5
|
+
sh 'git submodule update --init --recursive'
|
6
6
|
exec Gem.ruby, '-S', $PROGRAM_NAME, *ARGV
|
7
7
|
end
|
8
8
|
|
9
|
-
Gemgem.init(
|
9
|
+
Gemgem.init(__dir__) do |s|
|
10
10
|
require 'muack/version'
|
11
11
|
s.name = 'muack'
|
12
12
|
s.version = Muack::VERSION
|
13
|
-
%w[].each{ |g| s.add_runtime_dependency(g) }
|
14
13
|
end
|
data/lib/muack.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
|
2
2
|
require 'muack/session'
|
3
|
-
require 'muack/
|
3
|
+
require 'muack/satisfying'
|
4
4
|
|
5
5
|
module Muack
|
6
6
|
def self.verify obj=nil
|
@@ -46,20 +46,16 @@ module Muack
|
|
46
46
|
if block_given? then yield(ret) else ret end
|
47
47
|
end
|
48
48
|
|
49
|
-
def is_a klass
|
50
|
-
Muack::IsA.new(klass)
|
51
|
-
end
|
52
|
-
|
53
49
|
def anything
|
54
50
|
Muack::Anything.new
|
55
51
|
end
|
56
52
|
|
57
|
-
def
|
58
|
-
Muack::
|
53
|
+
def is_a klass
|
54
|
+
Muack::IsA.new(klass)
|
59
55
|
end
|
60
56
|
|
61
|
-
def
|
62
|
-
Muack::
|
57
|
+
def matching regexp
|
58
|
+
Muack::Matching.new(regexp)
|
63
59
|
end
|
64
60
|
|
65
61
|
def including element
|
@@ -70,12 +66,48 @@ module Muack
|
|
70
66
|
Muack::Within.new(range_or_array)
|
71
67
|
end
|
72
68
|
|
69
|
+
def responding_to *msg
|
70
|
+
Muack::RespondingTo.new(*msg)
|
71
|
+
end
|
72
|
+
|
73
|
+
def where spec
|
74
|
+
Muack::Where.new(spec)
|
75
|
+
end
|
76
|
+
|
77
|
+
def having spec
|
78
|
+
Muack::Having.new(spec)
|
79
|
+
end
|
80
|
+
|
81
|
+
def allowing spec
|
82
|
+
Muack::Allowing.new(spec)
|
83
|
+
end
|
84
|
+
|
85
|
+
def satisfying &block
|
86
|
+
Muack::Satisfying.new(&block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def match regexp
|
90
|
+
$stderr.puts("Muack::API.match is deprecated." \
|
91
|
+
" Use Muack::API.matching instead.")
|
92
|
+
matching(regexp)
|
93
|
+
end
|
94
|
+
|
73
95
|
def respond_to *msg
|
74
|
-
Muack::
|
96
|
+
$stderr.puts("Muack::API.respond_to is deprecated." \
|
97
|
+
" Use Muack::API.responding_to instead.")
|
98
|
+
responding_to(*msg)
|
99
|
+
end
|
100
|
+
|
101
|
+
def hash_including spec
|
102
|
+
$stderr.puts("Muack::API.hash_including is deprecated." \
|
103
|
+
" Use Muack::API.having instead.")
|
104
|
+
having(spec)
|
75
105
|
end
|
76
106
|
|
77
107
|
def satisfy &block
|
78
|
-
Muack::
|
108
|
+
$stderr.puts("Muack::API.satisfy is deprecated." \
|
109
|
+
" Use Muack::API.satisfying instead.")
|
110
|
+
satisfying(&block)
|
79
111
|
end
|
80
112
|
end
|
81
113
|
end
|
data/lib/muack/block.rb
CHANGED
@@ -1,17 +1,6 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
self.block, self.context = block, context
|
7
|
-
end
|
8
|
-
|
9
|
-
def call *args, &actual_block
|
10
|
-
if context # ruby: no way to pass actual_block to instance_exec
|
11
|
-
context.instance_exec(*args, &block)
|
12
|
-
else
|
13
|
-
block.call(*args, &actual_block)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
2
|
+
if RUBY_VERSION < '2.7'
|
3
|
+
require 'muack/block_26'
|
4
|
+
else
|
5
|
+
require 'muack/block_27'
|
17
6
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
module Muack
|
3
|
+
class Block < Struct.new(:block, :context)
|
4
|
+
def initialize block, context=nil
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(*args, &block)
|
9
|
+
if context
|
10
|
+
context.instance_exec(*args, &block)
|
11
|
+
else
|
12
|
+
block.call(*args, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/muack/coat.rb
CHANGED
data/lib/muack/definition.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
|
2
2
|
module Muack
|
3
|
-
Definition =
|
4
|
-
|
5
|
-
|
3
|
+
Definition = Struct.new(:msg, :args, :returns,
|
4
|
+
:peek_args, :peek_return,
|
5
|
+
:original_method, :visibility)
|
6
|
+
ActualCall = Struct.new(:msg, :args, :block)
|
6
7
|
WithAnyArgs = Object.new
|
7
8
|
end
|
data/lib/muack/error.rb
CHANGED
data/lib/muack/failure.rb
CHANGED
@@ -12,11 +12,12 @@ module Muack
|
|
12
12
|
|
13
13
|
class Unexpected < Failure
|
14
14
|
attr_reader :was
|
15
|
-
def initialize obj, expected_defis,
|
16
|
-
|
17
|
-
|
15
|
+
def initialize obj, expected_defis, actual_call
|
16
|
+
args = actual_call.args.map(&:inspect)
|
17
|
+
@was = "#{obj.inspect}.#{actual_call.msg}(#{args.join(', ')})"
|
18
|
+
|
18
19
|
if expected_defis.empty?
|
19
|
-
super("\nUnexpected call: #{
|
20
|
+
super("\nUnexpected call: #{was}")
|
20
21
|
else
|
21
22
|
build_expected(obj, expected_defis)
|
22
23
|
super("\nExpected: #{expected}\n but was: #{was}")
|
data/lib/muack/mock.rb
CHANGED
@@ -6,8 +6,6 @@ require 'muack/block'
|
|
6
6
|
require 'muack/error'
|
7
7
|
|
8
8
|
module Muack
|
9
|
-
EmptyBlock = proc{}
|
10
|
-
|
11
9
|
class Mock < BasicObject
|
12
10
|
attr_reader :object
|
13
11
|
def initialize object
|
@@ -24,8 +22,8 @@ module Muack
|
|
24
22
|
end
|
25
23
|
|
26
24
|
# Public API: Define mocked method
|
27
|
-
def method_missing msg, *args, &
|
28
|
-
defi = Definition.new(msg, args,
|
25
|
+
def method_missing msg, *args, &returns
|
26
|
+
defi = Definition.new(msg, args, returns)
|
29
27
|
if injected = __mock_injected[defi.msg]
|
30
28
|
defi.original_method = injected.original_method
|
31
29
|
else
|
@@ -51,49 +49,42 @@ module Muack
|
|
51
49
|
end
|
52
50
|
|
53
51
|
# used for mocked object to dispatch mocked method
|
54
|
-
def __mock_dispatch
|
55
|
-
if defi = __mock_defis[msg].shift
|
52
|
+
def __mock_dispatch actual_call
|
53
|
+
if defi = __mock_defis[actual_call.msg].shift
|
56
54
|
__mock_disps_push(defi)
|
57
|
-
if __mock_check_args(defi
|
55
|
+
if __mock_check_args(defi, actual_call)
|
58
56
|
defi
|
59
57
|
else
|
60
58
|
Mock.__send__(:raise, # Wrong argument
|
61
|
-
Unexpected.new(object, [defi],
|
59
|
+
Unexpected.new(object, [defi], actual_call))
|
62
60
|
end
|
63
61
|
else
|
64
|
-
|
65
|
-
if expected = defis.find{ |d| __mock_check_args(d.args, actual_args) }
|
66
|
-
Mock.__send__(:raise, # Too many times
|
67
|
-
Expected.new(object, expected, defis.size, defis.size+1))
|
68
|
-
else
|
69
|
-
Mock.__send__(:raise, # Wrong argument
|
70
|
-
Unexpected.new(object, defis, msg, actual_args))
|
71
|
-
end
|
62
|
+
__mock_failed(actual_call)
|
72
63
|
end
|
73
64
|
end
|
74
65
|
|
75
66
|
# used for mocked object to dispatch mocked method
|
76
|
-
def __mock_dispatch_call context, disp,
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
__mock_block_call(context, disp.returns,
|
86
|
-
args, actual_block, true)
|
87
|
-
elsif disp.original_method # proxies for singleton methods
|
88
|
-
context.__send__(disp.original_method, *args, &actual_block)
|
89
|
-
else # proxies for instance methods
|
90
|
-
# need the original context for calling `super`
|
91
|
-
# ruby: can't pass a block to yield, so we name it _yield
|
92
|
-
_yield.call(args, &actual_block)
|
93
|
-
end
|
67
|
+
def __mock_dispatch_call context, disp, actual_call, &proxy_super
|
68
|
+
# resolving arguments
|
69
|
+
call =
|
70
|
+
if disp.peek_args
|
71
|
+
args = __mock_block_call(context, disp.peek_args, actual_call)
|
72
|
+
ActualCall.new(actual_call.msg, args, actual_call.block)
|
73
|
+
else
|
74
|
+
actual_call
|
75
|
+
end
|
94
76
|
|
77
|
+
# retrieve actual return
|
78
|
+
ret =
|
79
|
+
if disp.returns
|
80
|
+
__mock_block_call(context, disp.returns, call)
|
81
|
+
else
|
82
|
+
__mock_proxy_call(context, disp, call, proxy_super)
|
83
|
+
end
|
84
|
+
|
85
|
+
# resolving return
|
95
86
|
if disp.peek_return
|
96
|
-
__mock_block_call(context, disp.peek_return, ret,
|
87
|
+
__mock_block_call(context, disp.peek_return, ret, true)
|
97
88
|
else
|
98
89
|
ret
|
99
90
|
end
|
@@ -102,9 +93,9 @@ module Muack
|
|
102
93
|
# used for Muack::Session#verify
|
103
94
|
def __mock_verify
|
104
95
|
__mock_defis.values.all?(&:empty?) || begin
|
105
|
-
msg, defis_with_same_msg = __mock_defis.find{ |_, v| v.
|
96
|
+
msg, defis_with_same_msg = __mock_defis.find{ |_, v| v.any? }
|
106
97
|
args, defis = defis_with_same_msg.group_by(&:args).first
|
107
|
-
dsize = __mock_disps[msg].
|
98
|
+
dsize = __mock_disps[msg].count{ |d| d.args == args }
|
108
99
|
Mock.__send__(:raise, # Too little times
|
109
100
|
Expected.new(object, defis.first, defis.size + dsize, dsize))
|
110
101
|
end
|
@@ -121,36 +112,46 @@ module Muack
|
|
121
112
|
private
|
122
113
|
def __mock_inject_method defi
|
123
114
|
__mock_injected[defi.msg] = defi
|
124
|
-
|
125
|
-
|
126
|
-
|
115
|
+
# a) ancestors.first is the first module in the method chain.
|
116
|
+
# it's just the singleton_class when nothing was prepended,
|
117
|
+
# otherwise the last prepended module.
|
118
|
+
# b) would be the class in AnyInstanceOf.
|
119
|
+
target = object.singleton_class.ancestors.first
|
120
|
+
Mock.store_original_method(target, defi)
|
121
|
+
__mock_inject_mock_method(target, defi)
|
127
122
|
end
|
128
123
|
|
129
124
|
def __mock_reset_method defi
|
130
|
-
object.singleton_class.module_eval do
|
125
|
+
object.singleton_class.ancestors.first.module_eval do
|
131
126
|
remove_method(defi.msg)
|
132
127
|
# restore original method
|
133
|
-
if
|
128
|
+
if public_instance_methods(false).include?(defi.original_method) ||
|
129
|
+
protected_instance_methods(false).include?(defi.original_method) ||
|
134
130
|
private_instance_methods(false).include?(defi.original_method)
|
135
131
|
alias_method(defi.msg, defi.original_method)
|
132
|
+
__send__(defi.visibility, defi.msg)
|
136
133
|
remove_method(defi.original_method)
|
137
134
|
end
|
138
135
|
end
|
139
136
|
end
|
140
137
|
|
141
138
|
def self.store_original_method klass, defi
|
142
|
-
|
143
|
-
:public
|
139
|
+
visibility = if klass.public_instance_methods(false).include?(defi.msg)
|
140
|
+
:public
|
141
|
+
elsif klass.protected_instance_methods(false).include?(defi.msg)
|
142
|
+
:protected
|
144
143
|
elsif klass.private_instance_methods(false).include?(defi.msg)
|
145
144
|
:private
|
146
145
|
end
|
147
146
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
147
|
+
if visibility # store original method
|
148
|
+
original_method = find_new_name(klass, defi.msg)
|
149
|
+
klass.__send__(:alias_method, original_method, defi.msg)
|
150
|
+
defi.original_method = original_method
|
151
|
+
defi.visibility = visibility
|
152
|
+
else
|
153
|
+
defi.visibility = :public
|
154
|
+
end
|
154
155
|
end
|
155
156
|
|
156
157
|
def self.find_new_name klass, message, level=0
|
@@ -166,40 +167,105 @@ module Muack
|
|
166
167
|
end
|
167
168
|
end
|
168
169
|
|
169
|
-
def __mock_inject_mock_method target, defi
|
170
|
+
def __mock_inject_mock_method target, defi
|
170
171
|
mock = self # remember the context
|
171
172
|
target.__send__(:define_method, defi.msg){ |*actual_args, &actual_block|
|
172
|
-
|
173
|
-
mock.
|
174
|
-
|
175
|
-
super
|
173
|
+
actual_call = ActualCall.new(defi.msg, actual_args, actual_block)
|
174
|
+
disp = mock.__mock_dispatch(actual_call)
|
175
|
+
mock.__mock_dispatch_call(self, disp, actual_call) do |call, has_kargs|
|
176
|
+
# need the original context for calling `super`
|
177
|
+
if has_kargs && kargs = call.args.last
|
178
|
+
super(*call.args[0...-1], **kargs, &call.block)
|
179
|
+
else
|
180
|
+
super(*call.args, &call.block)
|
181
|
+
end
|
176
182
|
end
|
177
183
|
}
|
178
|
-
target.__send__(
|
184
|
+
target.__send__(defi.visibility, defi.msg)
|
185
|
+
end
|
186
|
+
|
187
|
+
# used for __mock_dispatch
|
188
|
+
def __mock_failed actual_call, disps=__mock_disps[actual_call.msg]
|
189
|
+
if expected = __mock_find_checked_difi(disps, actual_call)
|
190
|
+
Mock.__send__(:raise, # Too many times
|
191
|
+
Expected.new(object, expected, disps.size, disps.size+1))
|
192
|
+
else
|
193
|
+
Mock.__send__(:raise, # Wrong argument
|
194
|
+
Unexpected.new(object, disps, actual_call))
|
195
|
+
end
|
179
196
|
end
|
180
197
|
|
181
198
|
# used for __mock_dispatch_call
|
182
|
-
def __mock_block_call context, block,
|
183
|
-
return unless block
|
199
|
+
def __mock_block_call context, block, actual_call, peek_return=false
|
184
200
|
# for AnyInstanceOf, we don't have the actual context at the time
|
185
201
|
# we're defining it, so we update it here
|
186
|
-
|
187
|
-
|
188
|
-
block.
|
189
|
-
else # peek_return doesn't need splat
|
190
|
-
block.call(actual_args, &actual_block)
|
202
|
+
if block.kind_of?(Block)
|
203
|
+
block.context = context
|
204
|
+
instance_exec_block = block.block
|
191
205
|
end
|
206
|
+
|
207
|
+
if peek_return # actual_call is the actual return in this case
|
208
|
+
block.call(actual_call, &instance_exec_block)
|
209
|
+
else
|
210
|
+
actual_block = actual_call.block || instance_exec_block
|
211
|
+
if __mock_block_with_kargs?(instance_exec_block || block) &&
|
212
|
+
kargs = actual_call.args.last
|
213
|
+
block.call(*actual_call.args[0...-1], **kargs, &actual_block)
|
214
|
+
else
|
215
|
+
block.call(*actual_call.args, &actual_block)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# used for __mock_dispatch_call
|
221
|
+
def __mock_proxy_call context, disp, call, proxy_super
|
222
|
+
if disp.original_method # proxies for singleton methods with __send__
|
223
|
+
if __mock_method_with_kargs?(context, disp.original_method) &&
|
224
|
+
kargs = call.args.last
|
225
|
+
context.__send__(
|
226
|
+
disp.original_method, *call.args[0...-1], **kargs, &call.block)
|
227
|
+
else
|
228
|
+
context.__send__(disp.original_method, *call.args, &call.block)
|
229
|
+
end
|
230
|
+
else # proxies for instance methods with super
|
231
|
+
proxy_super.call(call, __mock_super_with_kargs?(context, call.msg))
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def __mock_find_checked_difi defis, actual_call, meth=:find
|
236
|
+
defis.public_send(meth){ |d| __mock_check_args(d, actual_call) }
|
192
237
|
end
|
193
238
|
|
194
|
-
def
|
195
|
-
|
196
|
-
|
197
|
-
|
239
|
+
def __mock_method_with_kargs? object, method_name
|
240
|
+
__mock_block_with_kargs?(
|
241
|
+
::Kernel.instance_method(:method).bind(object).call(method_name))
|
242
|
+
end
|
243
|
+
|
244
|
+
def __mock_super_with_kargs? object, method_name
|
245
|
+
super_method =
|
246
|
+
::Kernel.instance_method(:method).bind(object).call(method_name).
|
247
|
+
super_method
|
248
|
+
|
249
|
+
super_method && __mock_block_with_kargs?(super_method)
|
250
|
+
end
|
251
|
+
|
252
|
+
def __mock_block_with_kargs? block
|
253
|
+
# there's no Symbol#start_with? in older Ruby
|
254
|
+
block.parameters.dig(-1, 0).to_s.start_with?('key')
|
255
|
+
end
|
256
|
+
|
257
|
+
def __mock_check_args defi, actual_call
|
258
|
+
return true if defi.args.size == 1 && defi.args.first == WithAnyArgs
|
259
|
+
|
260
|
+
expected_args = defi.args
|
261
|
+
actual_args = actual_call.args
|
262
|
+
|
263
|
+
if expected_args.none?{ |arg| arg.kind_of?(Satisfying) }
|
198
264
|
expected_args == actual_args
|
199
265
|
|
200
266
|
elsif expected_args.size == actual_args.size
|
201
267
|
expected_args.zip(actual_args).all?{ |(e, a)|
|
202
|
-
if e.kind_of?(
|
268
|
+
if e.kind_of?(Satisfying) then e.match(a) else e == a end
|
203
269
|
}
|
204
270
|
else
|
205
271
|
false
|