muack 1.3.0 → 1.5.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 +5 -5
- data/.travis.yml +19 -9
- data/CHANGES.md +39 -0
- data/Gemfile +0 -4
- data/README.md +33 -24
- data/Rakefile +3 -4
- 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/failure.rb +5 -4
- data/lib/muack/mock.rb +139 -67
- data/lib/muack/satisfying.rb +20 -11
- 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 +5 -7
- data/test/test_keyargs.rb +160 -0
- data/test/test_mock.rb +24 -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_satisfying.rb +39 -10
- data/test/test_spy.rb +149 -0
- data/test/test_stub.rb +0 -95
- data/test/test_visibility.rb +120 -0
- metadata +17 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 77dfffce2bb5df1930472ff39a8280bd9a1a69f6e81d1f0173d02099b5cdeaf9
|
4
|
+
data.tar.gz: aabb83e37e3ae995c02ad1ba1e938866cd3e036fa8a3f7682273a80255c60f90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01e57727b0e41455e69a5c4f0f6552b14d1efc192b08435d4b154abe68d86bf62e058cc54196ca05fb1519a301ae7c4fd3bc9924e25a2036c44d1b5a3c3a7843
|
7
|
+
data.tar.gz: 2324f6b087b7d79c46c5727078fce53a2f178ff8474bbed48af3750e447ad6e16168b788d148ca3c3a347a6a11ac578b923aa5effeb81013a9ab5e159d5508e3
|
data/.travis.yml
CHANGED
@@ -1,11 +1,21 @@
|
|
1
|
-
|
1
|
+
sudo: false
|
2
2
|
language: ruby
|
3
|
-
rvm:
|
4
|
-
- 2.0
|
5
|
-
- 2.1
|
6
|
-
- 2.2
|
7
|
-
- rbx-2
|
8
|
-
- jruby
|
9
3
|
|
10
|
-
install: 'bundle install --retry=3'
|
11
|
-
|
4
|
+
install: 'gem update --system; gem install bundler; bundle install --retry=3'
|
5
|
+
before_script: unset CI
|
6
|
+
script: 'ruby -vwr bundler/setup -S rake test'
|
7
|
+
|
8
|
+
matrix:
|
9
|
+
include:
|
10
|
+
- rvm: 2.4
|
11
|
+
env: RUBYOPT=--enable-frozen-string-literal
|
12
|
+
- rvm: 2.5
|
13
|
+
env: RUBYOPT=--enable-frozen-string-literal
|
14
|
+
- rvm: 2.6
|
15
|
+
env: RUBYOPT=--enable-frozen-string-literal
|
16
|
+
- rvm: 2.7
|
17
|
+
env: RUBYOPT=--enable-frozen-string-literal
|
18
|
+
- rvm: ruby-head
|
19
|
+
env: RUBYOPT=--enable-frozen-string-literal
|
20
|
+
- rvm: jruby
|
21
|
+
env: RUBYOPT=--enable-frozen-string-literal
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,44 @@
|
|
1
1
|
# CHANGES
|
2
2
|
|
3
|
+
## Muack 1.5.1 -- 2020-12-06
|
4
|
+
|
5
|
+
### Bugs fixed
|
6
|
+
|
7
|
+
* Eliminated potential keyword arguments warnings for `initialize` when
|
8
|
+
mocking against `new`.
|
9
|
+
|
10
|
+
## Muack 1.5.0 -- 2020-11-28
|
11
|
+
|
12
|
+
### Bugs fixed
|
13
|
+
|
14
|
+
* Properly handle prepended objects
|
15
|
+
* Properly restore method visibilities for singleton methods
|
16
|
+
|
17
|
+
### Enhancement
|
18
|
+
|
19
|
+
* Eliminated any potential keyword arguments warnings to be future proof
|
20
|
+
* Some major internal restructure
|
21
|
+
|
22
|
+
## Muack 1.4.0 -- 2015-11-21
|
23
|
+
|
24
|
+
### Incompatible changes / Enhancement
|
25
|
+
|
26
|
+
* Spies would now do pattern matching like stubs for matching
|
27
|
+
method arguments. This could be an incompatible change if you're
|
28
|
+
relying on spies checking the order for the same method calls.
|
29
|
+
This change would make spies and stubs more similar. If you are
|
30
|
+
really into a specific order, use mocks instead.
|
31
|
+
|
32
|
+
## Muack 1.3.2 -- 2015-06-11
|
33
|
+
|
34
|
+
* Fixed a bug for `where`, `having`, and `allowing` which should distinguish
|
35
|
+
between `nil` and `undefined` (which does not have a key in a hash)
|
36
|
+
|
37
|
+
## Muack 1.3.1 -- 2015-05-27
|
38
|
+
|
39
|
+
* Fixed a bug for `where`, `having`, and `allowing` which would raise an
|
40
|
+
exception whenever the actual value is not a hash or array.
|
41
|
+
|
3
42
|
## Muack 1.3.0 -- 2015-05-24
|
4
43
|
|
5
44
|
### Incompatible changes
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Muack [](http://travis-ci.org/godfat/muack) [](http://travis-ci.org/godfat/muack) [](https://coveralls.io/github/godfat/muack) [](https://gitter.im/godfat/muack)
|
2
2
|
|
3
3
|
by Lin Jen-Shin ([godfat](http://godfat.org))
|
4
4
|
|
@@ -7,6 +7,7 @@ by Lin Jen-Shin ([godfat](http://godfat.org))
|
|
7
7
|
* [github](https://github.com/godfat/muack)
|
8
8
|
* [rubygems](https://rubygems.org/gems/muack)
|
9
9
|
* [rdoc](http://rdoc.info/github/godfat/muack)
|
10
|
+
* [issues](https://github.com/godfat/muack/issues) (feel free to ask for support)
|
10
11
|
|
11
12
|
## DESCRIPTION:
|
12
13
|
|
@@ -25,7 +26,7 @@ Muack is much simpler and thus much faster and much more consistent.
|
|
25
26
|
|
26
27
|
## REQUIREMENTS:
|
27
28
|
|
28
|
-
* Tested with MRI (official CRuby)
|
29
|
+
* Tested with MRI (official CRuby) and JRuby.
|
29
30
|
|
30
31
|
## INSTALLATION:
|
31
32
|
|
@@ -39,14 +40,14 @@ Here's a quick example using [Pork][].
|
|
39
40
|
require 'pork/auto'
|
40
41
|
require 'muack'
|
41
42
|
|
42
|
-
include Muack::API
|
43
|
-
|
44
43
|
describe 'Hello' do
|
44
|
+
include Muack::API
|
45
|
+
|
45
46
|
before{ Muack.reset }
|
46
47
|
after { Muack.verify }
|
47
48
|
|
48
49
|
would 'say world!' do
|
49
|
-
str = 'Hello'
|
50
|
+
str = 'Hello'.dup
|
50
51
|
mock(str).say('!'){ |arg| "World#{arg}" }
|
51
52
|
str.say('!').should.eq 'World!'
|
52
53
|
end
|
@@ -98,7 +99,7 @@ p obj.name # 'obj'
|
|
98
99
|
p Muack.verify # true
|
99
100
|
```
|
100
101
|
|
101
|
-
Which is
|
102
|
+
Which is similar to using a stub with a spy:
|
102
103
|
|
103
104
|
``` ruby
|
104
105
|
obj = Object.new
|
@@ -143,6 +144,11 @@ p Muack.verify # true
|
|
143
144
|
However you should not mix mocks and stubs with the same method, or you
|
144
145
|
might encounter some unexpected result. Jump to _Caveat_ for more detail.
|
145
146
|
|
147
|
+
The other differences for stubs and spies, please check
|
148
|
+
[Pattern Matching for stubs and spies][pattern-matching].
|
149
|
+
In short, stubs and spies would do some kind of pattern matching,
|
150
|
+
making the order of the same method irrelevant.
|
151
|
+
|
146
152
|
On the other hand, stubs aren't limited to testing. If we want to monkey
|
147
153
|
patching something, stubs could be useful as we don't care how many times
|
148
154
|
the injected methods are called. Jump to _Muack as a mocky patching library_
|
@@ -167,7 +173,7 @@ affecting the others. This is helpful in the cases of mocking some very
|
|
167
173
|
basic objects like Time, without causing too much side effect.
|
168
174
|
|
169
175
|
``` ruby
|
170
|
-
name = 'str'
|
176
|
+
name = 'str'.dup
|
171
177
|
stub(name).to_s{ 'hi' }
|
172
178
|
stub(Time).new { Time.at(0) }
|
173
179
|
mock(Time).now { Time.new }
|
@@ -269,7 +275,7 @@ into proxy mode we simply do not provide any block to the injected method,
|
|
269
275
|
but just name it. Here's an example:
|
270
276
|
|
271
277
|
``` ruby
|
272
|
-
str = 'str'
|
278
|
+
str = 'str'.dup
|
273
279
|
mock(str).reverse
|
274
280
|
p str.reverse # 'rts'
|
275
281
|
p Muack.verify # true
|
@@ -279,7 +285,7 @@ Note that if reverse was not called exactly once, the mock would complain.
|
|
279
285
|
We could also use stub + spy to do the same thing as well:
|
280
286
|
|
281
287
|
``` ruby
|
282
|
-
str = 'str'
|
288
|
+
str = 'str'.dup
|
283
289
|
stub(str).reverse
|
284
290
|
p str.reverse # 'rts'
|
285
291
|
spy(str).reverse
|
@@ -326,7 +332,7 @@ If the order is reversed, then it would always return the original value,
|
|
326
332
|
because the proxy would always match, and Muack would stop searching the
|
327
333
|
next stub.
|
328
334
|
|
329
|
-
[pattern-matching]: #
|
335
|
+
[pattern-matching]: #pattern-matching-for-stubs-and-spies
|
330
336
|
|
331
337
|
#### any_instance_of mode
|
332
338
|
|
@@ -446,7 +452,7 @@ p Muack.verify # true
|
|
446
452
|
|
447
453
|
Note that it does not make sense to specify `times` for stubs, because
|
448
454
|
stubs don't care about times. Spies do, though. So this is also
|
449
|
-
|
455
|
+
similar to below:
|
450
456
|
|
451
457
|
``` ruby
|
452
458
|
obj = Object.new
|
@@ -618,7 +624,7 @@ our own behaviour, then we already have full control of the arguments.
|
|
618
624
|
There's no points to use both. This also applies to `peek_return`.
|
619
625
|
|
620
626
|
``` ruby
|
621
|
-
str = 'ff'
|
627
|
+
str = 'ff'.dup
|
622
628
|
mock(str).to_i.with_any_args.peek_args{ |radix| radix * 2 }
|
623
629
|
p str.to_i(8) # 255
|
624
630
|
p Muack.verify # true
|
@@ -649,7 +655,7 @@ might just want to take a look at the return? Here's an example using
|
|
649
655
|
`peek_return` to modify the original return value.
|
650
656
|
|
651
657
|
``` ruby
|
652
|
-
str = 'ff'
|
658
|
+
str = 'ff'.dup
|
653
659
|
mock(str).to_i.with_any_args.peek_return{ |int| int * 2 }
|
654
660
|
p str.to_i(16) # 510
|
655
661
|
p Muack.verify # true
|
@@ -708,6 +714,8 @@ obj.say{ |msg| p msg } # 'Hi'
|
|
708
714
|
p Muack.verify # true
|
709
715
|
```
|
710
716
|
|
717
|
+
#### Pattern Matching for stubs and spies
|
718
|
+
|
711
719
|
Moreover, we could also have stubs on the same method for different
|
712
720
|
arguments. We could think of this as a sort of pattern matching, and Muack
|
713
721
|
would try to find the best matched stub for us.
|
@@ -722,7 +730,8 @@ p Muack.verify # true
|
|
722
730
|
```
|
723
731
|
|
724
732
|
If `obj.find(2)` is called and Muack cannot find a matched stub, it would
|
725
|
-
raise a `Muack::Unexpected` and list the candidates for us.
|
733
|
+
raise a `Muack::Unexpected` and list the candidates for us. This also
|
734
|
+
applies to spies.
|
726
735
|
|
727
736
|
However, What if we don't want to be so exact? Then we should use verifiers.
|
728
737
|
We'll introduce each of them in next section. Note that verifiers
|
@@ -824,7 +833,7 @@ p Muack.verify # true
|
|
824
833
|
|
825
834
|
``` ruby
|
826
835
|
obj = Object.new
|
827
|
-
mock(obj).say(where(:a => is_a(
|
836
|
+
mock(obj).say(where(:a => is_a(Integer))){ |arg| arg }
|
828
837
|
p obj.say(:a => 0) # {:a => 0}
|
829
838
|
p Muack.verify # true
|
830
839
|
```
|
@@ -833,7 +842,7 @@ Note that this could be recursive.
|
|
833
842
|
|
834
843
|
``` ruby
|
835
844
|
obj = Object.new
|
836
|
-
mock(obj).say(where(:a => {:b => [is_a(
|
845
|
+
mock(obj).say(where(:a => {:b => [is_a(Integer)]})){ |arg| arg[:a] }
|
837
846
|
p obj.say(:a => {:b => [0]}) # {:b => [0]}
|
838
847
|
p Muack.verify # true
|
839
848
|
```
|
@@ -854,7 +863,7 @@ Note that this could be recursive.
|
|
854
863
|
|
855
864
|
``` ruby
|
856
865
|
obj = Object.new
|
857
|
-
mock(obj).say(having(:a => {:b => [is_a(
|
866
|
+
mock(obj).say(having(:a => {:b => [is_a(Integer)]})){ |arg| arg[:c] }
|
858
867
|
p obj.say(:a => {:b => [1]}, :c => 2) # 2
|
859
868
|
p Muack.verify # true
|
860
869
|
```
|
@@ -875,7 +884,7 @@ Note that this could be recursive.
|
|
875
884
|
|
876
885
|
``` ruby
|
877
886
|
obj = Object.new
|
878
|
-
mock(obj).say(allowing(:a => {:b => is_a(
|
887
|
+
mock(obj).say(allowing(:a => {:b => is_a(Integer), :c => 1})){ |arg| arg[:a] }
|
879
888
|
p obj.say(:a => {:b => 2}) # {:b => 2}
|
880
889
|
p Muack.verify # true
|
881
890
|
```
|
@@ -1114,7 +1123,7 @@ Consider we have two classes:
|
|
1114
1123
|
|
1115
1124
|
``` ruby
|
1116
1125
|
Food = Class.new
|
1117
|
-
User = Class.new
|
1126
|
+
User = Class.new(Struct.new(:food))
|
1118
1127
|
```
|
1119
1128
|
|
1120
1129
|
And we could make sure User#food is always a kind of `Food` by putting this
|
@@ -1141,7 +1150,7 @@ verifiers. For example, we could do this to check if the food is frozen:
|
|
1141
1150
|
|
1142
1151
|
``` ruby
|
1143
1152
|
Food = Class.new
|
1144
|
-
User = Class.new
|
1153
|
+
User = Class.new(Struct.new(:food))
|
1145
1154
|
|
1146
1155
|
FoodFrozen = Class.new(Muack::Satisfying) do
|
1147
1156
|
def match actual_arg
|
@@ -1201,8 +1210,8 @@ Or, I am happy to break legacy.
|
|
1201
1210
|
## USERS:
|
1202
1211
|
|
1203
1212
|
* [Rib][]
|
1204
|
-
* [rest-core](https://github.com/
|
1205
|
-
* [rest-more](https://github.com/
|
1213
|
+
* [rest-core](https://github.com/godfat/rest-core)
|
1214
|
+
* [rest-more](https://github.com/godfat/rest-more)
|
1206
1215
|
|
1207
1216
|
## CONTRIBUTORS:
|
1208
1217
|
|
@@ -1210,9 +1219,9 @@ Or, I am happy to break legacy.
|
|
1210
1219
|
|
1211
1220
|
## LICENSE:
|
1212
1221
|
|
1213
|
-
Apache License 2.0
|
1222
|
+
Apache License 2.0 (Apache-2.0)
|
1214
1223
|
|
1215
|
-
Copyright (c) 2013-
|
1224
|
+
Copyright (c) 2013-2020, Lin Jen-Shin (godfat)
|
1216
1225
|
|
1217
1226
|
Licensed under the Apache License, Version 2.0 (the "License");
|
1218
1227
|
you may not use this file except in compliance with the License.
|
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/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/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,35 +167,106 @@ 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
|
-
|
190
|
-
|
202
|
+
if block.kind_of?(Block)
|
203
|
+
block.context = context
|
204
|
+
instance_exec_block = block.block
|
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
|
191
217
|
end
|
192
218
|
end
|
193
219
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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) }
|
237
|
+
end
|
238
|
+
|
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
|
+
if super_method.owner == ::Class && super_method.name == :new
|
250
|
+
initialize_method = ::Class.instance_method(:instance_method).
|
251
|
+
bind(object).call(:initialize)
|
252
|
+
__mock_block_with_kargs?(initialize_method)
|
253
|
+
else
|
254
|
+
super_method && __mock_block_with_kargs?(super_method)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def __mock_block_with_kargs? block
|
259
|
+
# there's no Symbol#start_with? in older Ruby
|
260
|
+
block.parameters.dig(-1, 0).to_s.start_with?('key')
|
261
|
+
end
|
262
|
+
|
263
|
+
def __mock_check_args defi, actual_call
|
264
|
+
return true if defi.args.size == 1 && defi.args.first == WithAnyArgs
|
265
|
+
|
266
|
+
expected_args = defi.args
|
267
|
+
actual_args = actual_call.args
|
268
|
+
|
269
|
+
if expected_args.none?{ |arg| arg.kind_of?(Satisfying) }
|
198
270
|
expected_args == actual_args
|
199
271
|
|
200
272
|
elsif expected_args.size == actual_args.size
|