much-stub 0.1.2 → 0.1.7

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
2
  SHA256:
3
- metadata.gz: 948b3a6fa21e50a2a221b167fe768a5d65c7a61c135622fe6ced882a5e406b03
4
- data.tar.gz: 700d700fab285da4620cb4fbd3b521a58a9ffeaf8d50afdcc96780810091e301
3
+ metadata.gz: 15204360d9518246e4abaa703670f75489b26364a262f61397956bcd6292bb91
4
+ data.tar.gz: e53db7a648e52be63ea31d6dc930d7c009e65604d05a4e1e447edcfb3ef7b296
5
5
  SHA512:
6
- metadata.gz: fecb7b76d76bc9ff338461c7ec6a48ac3bf1833bd77f4f248c9d4a145fa0b0177b3ce9d99d01add632266d7f9c5731441f0ad553f14775e7cf704aaffc80099a
7
- data.tar.gz: 55308114a2f7aac2294ba44aac2adf84152cc2213a40a9ff0118b3fe2e8d43738cbc617fb19855c63515e915bc46754fc915d86b568d7a9c97356fb619420156
6
+ metadata.gz: 5892e22dc64f46400a115d42fb7b7da3d5b82387fda5b8c2ec5f807d6a9b47e4dca5a92d86a3a97898d5f6e85885808065d591e53aba7d78303afc1396fce4a4
7
+ data.tar.gz: 4476a777e7ee869f59080f5c0bc40d2d8736d77d01588f193ea6e826f424d3abf110ca49a60437267e7e16d5578c2f6f74bb24ce810940af82154ce0e08e7199
data/.l.yml ADDED
@@ -0,0 +1,8 @@
1
+ # https://github.com/redding/l.rb
2
+
3
+ linters:
4
+ - name: "Rubocop"
5
+ cmd: "bundle exec rubocop"
6
+ extensions:
7
+ - ".rb"
8
+ cli_abbrev: "u"
@@ -0,0 +1,3 @@
1
+ inherit_gem:
2
+ much-style-guide:
3
+ - "lib/much-style-guide/rubocop.yml"
@@ -0,0 +1 @@
1
+ 2.5.8
data/.t.yml ADDED
@@ -0,0 +1,6 @@
1
+ # https://github.com/redding/t.rb
2
+
3
+ default_cmd: bundle exec assert
4
+ test_dir: test
5
+ test_file_suffixes:
6
+ - "_tests.rb"
data/Gemfile CHANGED
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
5
+ ruby "~> 2.5"
6
+
3
7
  gemspec
4
8
 
5
- gem "pry", "~> 0.12.2"
9
+ gem "pry"
data/README.md CHANGED
@@ -110,24 +110,49 @@ my_object = my_class.new
110
110
 
111
111
  basic_method_called_with = nil
112
112
  MuchStub.(my_object, :basic_method) { |*args|
113
- basic_method_called_with = args
113
+ basic_method_called_with = MuchStub::Call.new(*args)
114
+ }
115
+ # OR
116
+ MuchStub.(my_object, :basic_method).on_call { |call|
117
+ basic_method_called_with = call
118
+ }
119
+ # OR
120
+ MuchStub.on_call(my_object, :basic_method) { |call|
121
+ # MucStub.on_call(...) { ... } is equivalent to
122
+ # MuchStub.(...).on_call { ... }
123
+ basic_method_called_with = call
114
124
  }
115
125
 
116
126
  my_object.basic_method(123)
117
- basic_method_called_with
127
+ basic_method_called_with.args
118
128
  # => [123]
119
129
 
120
- iterator_method_call_args = nil
121
- iterator_method_call_block = nil
130
+ basic_method_called_with = nil
131
+ MuchStub.(my_object, :basic_method).with(4, 5, 6) { |*args|
132
+ basic_method_called_with = MuchStub::Call.new(*args)
133
+ }
134
+ # OR
135
+ MuchStub.(my_object, :basic_method).with(4, 5, 6).on_call { |call|
136
+ basic_method_called_with = call
137
+ }
138
+
139
+ my_object.basic_method(4, 5, 6)
140
+ basic_method_called_with.args
141
+ # => [4,5,6]
142
+
143
+ iterator_method_called_with = nil
122
144
  MuchStub.(my_object, :iterator_method) { |*args, &block|
123
- iterator_method_call_args = args
124
- iterator_method_call_block = block
145
+ iterator_method_called_with = MuchStub::Call.new(*args)
146
+ }
147
+ # OR
148
+ MuchStub.(my_object, :iterator_method).on_call { |call|
149
+ iterator_method_called_with = call
125
150
  }
126
151
 
127
152
  my_object.iterator_method([1, 2, 3], &:to_s)
128
- iterator_method_call_args
153
+ iterator_method_called_with.args
129
154
  # => [[1, 2, 3]]
130
- iterator_method_call_block
155
+ iterator_method_called_with.block
131
156
  # => #<Proc:0x00007fb083a6feb0(&:to_s)>
132
157
 
133
158
  # Count method calls for spying.
@@ -145,13 +170,17 @@ basic_method_call_count
145
170
 
146
171
  basic_method_calls = []
147
172
  MuchStub.(my_object, :basic_method) { |*args|
148
- basic_method_calls << args
173
+ basic_method_calls << MuchStub::Call.new(*args)
174
+ }
175
+ # OR
176
+ MuchStub.(my_object, :basic_method).on_call { |call|
177
+ basic_method_calls << call
149
178
  }
150
179
 
151
180
  my_object.basic_method(123)
152
181
  basic_method_calls.size
153
182
  # => 1
154
- basic_method_calls.first
183
+ basic_method_calls.first.args
155
184
  # => [123]
156
185
  ```
157
186
 
@@ -218,12 +247,16 @@ basic_method_called_with
218
247
 
219
248
  basic_method_called_with = nil
220
249
  MuchStub.tap(my_object, :basic_method) { |value, *args|
221
- basic_method_called_with = args
250
+ basic_method_called_with = MuchStub::Call.new(*args)
251
+ }
252
+ # OR
253
+ MuchStub.tap_on_call(my_object, :basic_method) { |value, call|
254
+ basic_method_called_with = call
222
255
  }
223
256
 
224
257
  my_object.basic_method(123)
225
258
  # => "123"
226
- basic_method_called_with
259
+ basic_method_called_with.args
227
260
  # => [123]
228
261
  ```
229
262
 
@@ -266,6 +299,39 @@ thing.value
266
299
  # => 456
267
300
  ```
268
301
 
302
+ ### `MuchStub.spy`
303
+
304
+ Use the `.spy` method to spy on method calls. This is especially helpful for spying on _chained_ method calls.
305
+
306
+ ```ruby
307
+ # Given this object/API
308
+
309
+ myclass = Class.new do
310
+ def one; self; end
311
+ def two(val); self; end
312
+ def three; self; end
313
+ def ready?; false; end
314
+ end
315
+ myobj = myclass.new
316
+
317
+ spy =
318
+ MuchStub.spy(myobj :one, :two, :three, ready?: true)
319
+
320
+ assert_equal spy, myobj.one
321
+ assert_equal spy, myobj.two("a")
322
+ assert_equal spy, myobj.three
323
+
324
+ assert_true myobj.one.two("b").three.ready?
325
+
326
+ assert_kind_of MuchStub::CallSpy, spy
327
+ assert_equal 2, spy.one_call_count
328
+ assert_equal 2, spy.two_call_count
329
+ assert_equal 2, spy.three_call_count
330
+ assert_equal 1, spy.ready_predicate_call_count
331
+ assert_equal ["b"], spy.two_last_called_with.args
332
+ assert_true spy.ready_predicate_called?
333
+ ```
334
+
269
335
  ## Installation
270
336
 
271
337
  Add this line to your application's Gemfile:
@@ -1,5 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "much-stub/version"
2
4
 
5
+ require "much-stub/call"
6
+ require "much-stub/call_spy"
7
+
3
8
  module MuchStub
4
9
  def self.stubs
5
10
  @stubs ||= {}
@@ -9,39 +14,72 @@ module MuchStub
9
14
  MuchStub::Stub.key(obj, meth)
10
15
  end
11
16
 
12
- def self.call(*args, &block)
13
- self.stub(*args, &block)
17
+ def self.arity_matches?(method, args)
18
+ # mandatory args
19
+ return true if method.arity == args.size
20
+ # variable args
21
+ return true if method.arity < 0 && args.size >= (method.arity + 1).abs
22
+
23
+ false
14
24
  end
15
25
 
16
26
  def self.stub(obj, meth, &block)
17
- key = self.stub_key(obj, meth)
18
- self.stubs[key] ||= MuchStub::Stub.new(obj, meth, caller_locations)
19
- self.stubs[key].tap{ |s| s.do = block }
27
+ key = stub_key(obj, meth)
28
+ stubs[key] ||= MuchStub::Stub.new(obj, meth, caller_locations)
29
+ stubs[key].tap{ |s| s.do = block }
30
+ end
31
+
32
+ def self.call(*args, &block)
33
+ stub(*args, &block)
34
+ end
35
+
36
+ def self.stub_on_call(*args, &on_call_block)
37
+ stub(*args).on_call(&on_call_block)
38
+ end
39
+
40
+ def self.on_call(*args, &on_call_block)
41
+ stub_on_call(*args, &on_call_block)
20
42
  end
21
43
 
22
44
  def self.unstub(obj, meth)
23
- key = self.stub_key(obj, meth)
24
- (self.stubs.delete(key) || MuchStub::NullStub.new).teardown
45
+ key = stub_key(obj, meth)
46
+ (stubs.delete(key) || MuchStub::NullStub.new).teardown
25
47
  end
26
48
 
27
49
  def self.unstub!
28
- self.stubs.keys.each{ |key| self.stubs.delete(key).teardown }
50
+ stubs.keys.each{ |key| stubs.delete(key).teardown }
29
51
  end
30
52
 
31
53
  def self.stub_send(obj, meth, *args, &block)
32
54
  orig_caller = caller_locations
33
- stub = self.stubs.fetch(MuchStub::Stub.key(obj, meth)) do
55
+ stub = stubs.fetch(MuchStub::Stub.key(obj, meth)) do
34
56
  raise NotStubbedError, "`#{meth}` not stubbed.", orig_caller.map(&:to_s)
35
57
  end
36
58
  stub.call_method(args, &block)
37
59
  end
38
60
 
39
61
  def self.tap(obj, meth, &tap_block)
40
- self.stub(obj, meth) { |*args, &block|
41
- self.stub_send(obj, meth, *args, &block).tap { |value|
42
- tap_block.call(value, *args, &block) if tap_block
43
- }
44
- }
62
+ stub(obj, meth) do |*args, &block|
63
+ stub_send(obj, meth, *args, &block).tap do |value|
64
+ tap_block&.call(value, *args, &block)
65
+ end
66
+ end
67
+ end
68
+
69
+ def self.tap_on_call(obj, meth, &on_call_block)
70
+ tap(obj, meth) do |value, *args, &block|
71
+ on_call_block&.call(value, MuchStub::Call.new(*args, &block))
72
+ end
73
+ end
74
+
75
+ def self.spy(obj, *meths, **return_values)
76
+ MuchStub::CallSpy.new(**return_values).call_spy_tap do |spy|
77
+ meths.each do |meth|
78
+ stub(obj, meth) do |*args, &block|
79
+ spy.__send__(meth, *args, &block)
80
+ end
81
+ end
82
+ end
45
83
  end
46
84
 
47
85
  class Stub
@@ -75,11 +113,15 @@ module MuchStub
75
113
 
76
114
  def call(args, orig_caller = nil, &block)
77
115
  orig_caller ||= caller_locations
78
- unless arity_matches?(args)
79
- msg = "arity mismatch on `#{@method_name}`: " \
80
- "expected #{number_of_args(@method.arity)}, " \
81
- "called with #{args.size}"
82
- raise StubArityError, msg, orig_caller.map(&:to_s)
116
+ unless MuchStub.arity_matches?(@method, args)
117
+ raise(
118
+ StubArityError.new(
119
+ @method,
120
+ args,
121
+ method_name: @method_name,
122
+ backtrace: orig_caller,
123
+ ),
124
+ )
83
125
  end
84
126
  lookup(args, orig_caller).call(*args, &block)
85
127
  rescue NotStubbedError
@@ -89,13 +131,31 @@ module MuchStub
89
131
 
90
132
  def with(*args, &block)
91
133
  orig_caller = caller_locations
92
- unless arity_matches?(args)
93
- msg = "arity mismatch on `#{@method_name}`: " \
94
- "expected #{number_of_args(@method.arity)}, " \
95
- "stubbed with #{args.size}"
96
- raise StubArityError, msg, orig_caller.map(&:to_s)
134
+ unless MuchStub.arity_matches?(@method, args)
135
+ raise(
136
+ StubArityError.new(
137
+ @method,
138
+ args,
139
+ method_name: @method_name,
140
+ backtrace: orig_caller,
141
+ ),
142
+ )
97
143
  end
98
144
  @lookup[args] = block
145
+ self
146
+ end
147
+
148
+ def on_call(&on_call_block)
149
+ stub_block =
150
+ ->(*args, &block){
151
+ on_call_block&.call(MuchStub::Call.new(*args, &block))
152
+ }
153
+ if @lookup.empty?
154
+ @do = stub_block
155
+ elsif @lookup.value?(nil)
156
+ @lookup.transform_values!{ |value| value.nil? ? stub_block : value }
157
+ end
158
+ self
99
159
  end
100
160
 
101
161
  def teardown
@@ -106,7 +166,7 @@ module MuchStub
106
166
  end
107
167
 
108
168
  def inspect
109
- "#<#{self.class}:#{"0x0%x" % (object_id << 1)}" \
169
+ "#<#{self.class}:#{format("0x0%x", (object_id << 1))}" \
110
170
  " @method_name=#{@method_name.inspect}" \
111
171
  ">"
112
172
  end
@@ -118,7 +178,7 @@ module MuchStub
118
178
  msg = "#{object.inspect} does not respond to `#{@method_name}`"
119
179
  raise StubError, msg, orig_caller.map(&:to_s)
120
180
  end
121
- is_constant = object.kind_of?(Module)
181
+ is_constant = object.is_a?(Module)
122
182
  local_object_methods = object.methods(false).map(&:to_s)
123
183
  all_object_methods = object.methods.map(&:to_s)
124
184
  if (is_constant && !local_object_methods.include?(@method_name)) ||
@@ -129,7 +189,8 @@ module MuchStub
129
189
  method
130
190
  end
131
191
 
132
- if !local_object_methods.include?(@name) # already stubbed
192
+ # already stubbed
193
+ unless local_object_methods.include?(@name)
133
194
  @metaclass.send(:alias_method, @name, @method_name)
134
195
  end
135
196
  @method = object.method(@name)
@@ -144,20 +205,20 @@ module MuchStub
144
205
 
145
206
  def lookup(args, orig_caller)
146
207
  @lookup.fetch(args) do
147
- self.do || begin
208
+ self.do ||
209
+ begin
148
210
  msg = "#{inspect_call(args)} not stubbed."
149
211
  inspect_lookup_stubs.tap do |stubs|
150
- msg += "\nStubs:\n#{stubs}" if !stubs.empty?
212
+ msg += "\nStubs:\n#{stubs}" unless stubs.empty?
151
213
  end
152
214
  raise NotStubbedError, msg, orig_caller.map(&:to_s)
153
215
  end
154
- end
155
- end
156
-
157
- def arity_matches?(args)
158
- return true if @method.arity == args.size # mandatory args
159
- return true if @method.arity < 0 && args.size >= (@method.arity+1).abs # variable args
160
- return false
216
+ end ||
217
+ raise(
218
+ StubError,
219
+ "#{inspect_call(args)} stubbed with no block.",
220
+ orig_caller.map(&:to_s),
221
+ )
161
222
  end
162
223
 
163
224
  def inspect_lookup_stubs
@@ -167,23 +228,38 @@ module MuchStub
167
228
  def inspect_call(args)
168
229
  "`#{@method_name}(#{args.map(&:inspect).join(",")})`"
169
230
  end
170
-
171
- def number_of_args(arity)
172
- if arity < 0
173
- "at least #{(arity + 1).abs}"
174
- else
175
- arity
176
- end
177
- end
178
231
  end
179
232
 
180
233
  StubError = Class.new(ArgumentError)
181
234
  NotStubbedError = Class.new(StubError)
182
- StubArityError = Class.new(StubError)
235
+ StubArityError =
236
+ Class.new(StubError) do
237
+ def initialize(method, args, method_name:, backtrace:)
238
+ msg = "arity mismatch on `#{method_name}`: " \
239
+ "expected #{number_of_args(method.arity)}, " \
240
+ "called with #{args.size}"
183
241
 
184
- NullStub = Class.new do
185
- def teardown; end # no-op
186
- end
242
+ super(msg)
243
+ set_backtrace(Array(backtrace).map(&:to_s))
244
+ end
245
+
246
+ private
247
+
248
+ def number_of_args(arity)
249
+ if arity < 0
250
+ "at least #{(arity + 1).abs}"
251
+ else
252
+ arity
253
+ end
254
+ end
255
+ end
256
+
257
+ NullStub =
258
+ Class.new do
259
+ def teardown
260
+ # no-op
261
+ end
262
+ end
187
263
 
188
264
  module ParameterList
189
265
  LETTERS = ("a".."z").to_a.freeze
@@ -196,8 +272,6 @@ module MuchStub
196
272
  params.join(", ")
197
273
  end
198
274
 
199
- private
200
-
201
275
  def self.get_arity(object, method_name)
202
276
  object.method(method_name).arity
203
277
  rescue NameError