muack 1.3.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3bdc0fed7c77d1ef5a925dc20e54857bc2bf279b
4
- data.tar.gz: f5be7c8560523fe958c91683d6401e0cfc52f9de
2
+ SHA256:
3
+ metadata.gz: 77dfffce2bb5df1930472ff39a8280bd9a1a69f6e81d1f0173d02099b5cdeaf9
4
+ data.tar.gz: aabb83e37e3ae995c02ad1ba1e938866cd3e036fa8a3f7682273a80255c60f90
5
5
  SHA512:
6
- metadata.gz: d32c303896f89c12a83a84602e1190062c53d89cb12e10aaff8fb05dc5382d98f82b9e2c8a9a5ae53fee6a284ecdf4bb7b96e10034b33347bce227a071eabbb1
7
- data.tar.gz: 3d028e15246b9c8bdd79ab39accfcfb55ec68d5997c502744510a3093a75e8d4e4fa05da936c091fb32cd43dc0429e88cd2841157285fb08dbd3fe2da6d6b1fa
6
+ metadata.gz: 01e57727b0e41455e69a5c4f0f6552b14d1efc192b08435d4b154abe68d86bf62e058cc54196ca05fb1519a301ae7c4fd3bc9924e25a2036c44d1b5a3c3a7843
7
+ data.tar.gz: 2324f6b087b7d79c46c5727078fce53a2f178ff8474bbed48af3750e447ad6e16168b788d148ca3c3a347a6a11ac578b923aa5effeb81013a9ab5e159d5508e3
@@ -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
- script: 'ruby -r bundler/setup -S rake test'
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
@@ -8,7 +8,3 @@ gem 'pork'
8
8
 
9
9
  gem 'simplecov', :require => false if ENV['COV']
10
10
  gem 'coveralls', :require => false if ENV['CI']
11
-
12
- platform :rbx do
13
- gem 'rubysl-singleton' # used in rake
14
- end
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Muack [![Build Status](https://secure.travis-ci.org/godfat/muack.png?branch=master)](http://travis-ci.org/godfat/muack) [![Coverage Status](https://coveralls.io/repos/godfat/muack/badge.png?branch=master)](https://coveralls.io/r/godfat/muack?branch=master)
1
+ # Muack [![Build Status](https://secure.travis-ci.org/godfat/muack.png?branch=master)](http://travis-ci.org/godfat/muack) [![Coverage Status](https://coveralls.io/repos/github/godfat/muack/badge.png)](https://coveralls.io/github/godfat/muack) [![Join the chat at https://gitter.im/godfat/muack](https://badges.gitter.im/Join%20Chat.svg)](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), Rubinius and JRuby.
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 roughly semantically equivalent to using a stub with a spy:
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]: #arguments-verifiers-satisfy
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
- semantically equivalent to below:
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(Fixnum))){ |arg| arg }
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(Fixnum)]})){ |arg| arg[: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(Fixnum)]})){ |arg| arg[:c] }
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(Fixnum), :c => 1})){ |arg| arg[: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{ attr_accessor :food }
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{ attr_accessor :food }
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/cardinalblue/rest-core)
1205
- * [rest-more](https://github.com/cardinalblue/rest-more)
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-2015, Lin Jen-Shin (godfat)
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 "#{dir = File.dirname(__FILE__)}/task/gemgem"
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(dir) do |s|
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
@@ -1,17 +1,6 @@
1
1
 
2
- module Muack
3
- class Block
4
- attr_accessor :block, :context
5
- def initialize block, context=nil
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
@@ -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(...)
9
+ if context
10
+ context.instance_exec(...)
11
+ else
12
+ block.call(...)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -4,7 +4,7 @@ require 'muack/mock'
4
4
  module Muack
5
5
  class Coat < Mock
6
6
  # used for mocked object to dispatch mocked method
7
- def __mock_dispatch msg, actual_args
7
+ def __mock_dispatch actual_call
8
8
  defi = super
9
9
  if __mock_defis[defi.msg].empty?
10
10
  __mock_reset_method(defi)
@@ -1,7 +1,8 @@
1
1
 
2
2
  module Muack
3
- Definition = Class.new(Struct.new(:msg, :args, :returns,
4
- :peek_args, :peek_return,
5
- :original_method))
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
@@ -12,11 +12,12 @@ module Muack
12
12
 
13
13
  class Unexpected < Failure
14
14
  attr_reader :was
15
- def initialize obj, expected_defis, msg, args
16
- @was = "#{obj.inspect}.#{msg}(" \
17
- "#{args.map(&:inspect).join(', ')})"
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: #{@was}")
20
+ super("\nUnexpected call: #{was}")
20
21
  else
21
22
  build_expected(obj, expected_defis)
22
23
  super("\nExpected: #{expected}\n but was: #{was}")
@@ -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, &block
28
- defi = Definition.new(msg, args, block)
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 msg, actual_args
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.args, actual_args)
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], msg, actual_args))
59
+ Unexpected.new(object, [defi], actual_call))
62
60
  end
63
61
  else
64
- defis = __mock_disps[msg]
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, actual_args, actual_block, &_yield
77
- args = if disp.peek_args
78
- __mock_block_call(context, disp.peek_args,
79
- actual_args, actual_block, true)
80
- else
81
- actual_args
82
- end
83
-
84
- ret = if disp.returns
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, EmptyBlock, false)
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.size > 0 }
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].select{ |d| d.args == args }.size
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
- target = object.singleton_class # would be the class in AnyInstanceOf
125
- privilege = Mock.store_original_method(target, defi)
126
- __mock_inject_mock_method(target, defi, privilege)
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 instance_methods(false).include?(defi.original_method) ||
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
- privilege = if klass.instance_methods(false).include?(defi.msg)
143
- :public # TODO: forget about protected methods?
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
- return :public unless privilege
149
- # store original method
150
- original_method = find_new_name(klass, defi.msg)
151
- klass.__send__(:alias_method, original_method, defi.msg)
152
- defi.original_method = original_method
153
- privilege
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, privilege=:public
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
- disp = mock.__mock_dispatch(defi.msg, actual_args)
173
- mock.__mock_dispatch_call(self, disp, actual_args,
174
- actual_block) do |args, &block|
175
- super(*args, &block)
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__(privilege, defi.msg)
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, actual_args, actual_block, splat
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
- block.context = context if block.kind_of?(Block)
187
- if splat
188
- block.call(*actual_args, &actual_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
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
- def __mock_check_args expected_args, actual_args
195
- if expected_args == [WithAnyArgs]
196
- true
197
- elsif expected_args.none?{ |arg| arg.kind_of?(Satisfying) }
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