grift 2.0.0 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62cac26275a4af88278a38985c4edc2e3d001954fe68e491e242add325e49576
4
- data.tar.gz: 03ddad7645f758c5708e353e8780001cfd4ce7b478cee2a4c5d60ae7a180cb80
3
+ metadata.gz: be076a2312ee3278d68f393d8ace60b12aa44cec9923336fbe0469366be5c9cd
4
+ data.tar.gz: b38846285534aaf9e56c413c77b84da307655a1ac336aba9c8a4fa3b676a34c7
5
5
  SHA512:
6
- metadata.gz: 8df483db6759b17e6c8d7d6f05a81ed3b507b4be7d9a3d5b0f0adaa70bc8fec798d8d3e750d6f512a586e128ca0f492288d5b32a6e772a8ca6e2a67903116f41
7
- data.tar.gz: c6460882f099ec2622fe92b99882e52b54df86c5aaf86311f86104326a94455235890ca5cc161448f8b7d5ab60d6853c1d65287764b8516eeebb5fd53ba3c8e3
6
+ metadata.gz: 25cdd247a6c4eacff1c77b099d22475bedb56d7a885beb1365e71e6798bddee87401fe1257e65cfbb5af5137b8458af650f2916f32b000a0712c3494e3f45459
7
+ data.tar.gz: 2c0e207ea0c7eb8db2719af429aaf0831fcb18169f5167d92f0111207e5a228e9f2a5d49131565dd499043268b65c4481fbd4497dca918c993326d7ed683102f
@@ -15,12 +15,12 @@ jobs:
15
15
  BUNDLE_WITHOUT: development:test
16
16
 
17
17
  steps:
18
- - uses: actions/checkout@v2
18
+ - uses: actions/checkout@v3
19
19
 
20
- - name: Set up Ruby 2.7
20
+ - name: Set up Ruby 3.1
21
21
  uses: ruby/setup-ruby@v1
22
22
  with:
23
- ruby-version: 2.7
23
+ ruby-version: 3.1
24
24
  bundler-cache: true
25
25
 
26
26
  - name: Rubocop
@@ -33,10 +33,10 @@ jobs:
33
33
  strategy:
34
34
  fail-fast: false
35
35
  matrix:
36
- ruby: ["2.7", "3.0", "3.1"]
36
+ ruby: ["2.7", "3.0", "3.1", "3.2"]
37
37
 
38
38
  steps:
39
- - uses: actions/checkout@v2
39
+ - uses: actions/checkout@v3
40
40
  - uses: ruby/setup-ruby@v1
41
41
  with:
42
42
  ruby-version: ${{ matrix.ruby }}
@@ -47,7 +47,7 @@ jobs:
47
47
  run: bundle exec rake test
48
48
 
49
49
  - name: Upload coverage to Codecov
50
- uses: codecov/codecov-action@v2
50
+ uses: codecov/codecov-action@v3
51
51
  with:
52
52
  token: ${{ secrets.CODECOV_TOKEN }}
53
53
  files: ./coverage/.resultset.json
data/.rubocop.yml CHANGED
@@ -102,12 +102,12 @@ Lint/AmbiguousRange:
102
102
 
103
103
  # Metrics
104
104
  Metrics/AbcSize:
105
- Max: 20
105
+ Max: 25
106
106
  Exclude:
107
107
  - "test/**/*"
108
108
 
109
109
  Metrics/ClassLength:
110
- Max: 150
110
+ Max: 225
111
111
  CountAsOne:
112
112
  - array
113
113
  - hash
@@ -128,6 +128,9 @@ Metrics/MethodLength:
128
128
  Minitest/AssertPredicate:
129
129
  Enabled: false
130
130
 
131
+ Minitest/EmptyLineBeforeAssertionMethods:
132
+ Enabled: false
133
+
131
134
  Minitest/MultipleAssertions:
132
135
  Max: 10
133
136
 
@@ -138,6 +141,10 @@ Minitest/RefutePredicate:
138
141
  Naming/InclusiveLanguage:
139
142
  Enabled: true
140
143
 
144
+ Naming/MethodParameterName:
145
+ AllowedNames:
146
+ - n
147
+
141
148
  Naming/VariableNumber:
142
149
  EnforcedStyle: snake_case
143
150
 
data/CHANGELOG.md CHANGED
@@ -8,8 +8,26 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
8
8
 
9
9
  None
10
10
 
11
+ ## [2.1.0](https://github.com/clarkedb/grift/releases/tag/v2.1.0) - 2022-12-27
12
+
13
+ ### Added
14
+
15
+ * Official support for Ruby 3.2 ([#126](https://github.com/clarkedb/grift/pull/126))
16
+ * Support for finite/self-terminating mocking for more precision in testing where a method may get called multiple times
17
+ + The Grift API now supports `mock_return_value_once`, `mock_return_value_n_times`, and `mock_return_values_in_order` ([#135](https://github.com/clarkedb/grift/pull/135))
18
+ + The Grift API now supports `mock_implementation_once` and `mock_implementation_n_times` ([#136](https://github.com/clarkedb/grift/pull/136))
19
+
20
+ ## [2.0.1](https://github.com/clarkedb/grift/releases/tag/v2.0.1) - 2022-03-27
21
+
22
+ ### Fixed
23
+
24
+ * When spying on a method that takes a block, the block now gets forwarded to the original method ([#78](https://github.com/clarkedb/grift/pull/78))
25
+ * When mocking the implementation, if a block is not provided a `Grift::Error` is raised instead of a `LocalJumpError` ([#77](https://github.com/clarkedb/grift/pull/77))
26
+
11
27
  ## [2.0.0](https://github.com/clarkedb/grift/releases/tag/v2.0.0) - 2022-03-14
12
28
 
29
+ This version adds true keyword argument support for Ruby 3. See below for how to handle breaking changes.
30
+
13
31
  ### Changed
14
32
 
15
33
  * Dropped support for Ruby 2.5 ([#69](https://github.com/clarkedb/grift/pull/69))
@@ -17,7 +35,7 @@ None
17
35
  * To support keyword arguments, records of call arguments are no longer stored in simple arrays but in a custom Enumerable ([#72](https://github.com/clarkedb/grift/pull/72))
18
36
  + This changes the way that your tests will interact with mock calls
19
37
  + When before `calls` returned an array, it returns a `Grift::MockMethod::MockExecutions::MockArguments` object
20
- + Migrating to maintain previous behavior just requires appending `.args` to `calls`
38
+ + Migrating to maintain previous behavior just requires appending `.args` to `calls[i]`
21
39
 
22
40
  ### Added
23
41
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grift (2.0.0)
4
+ grift (2.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,53 +10,56 @@ GEM
10
10
  ast (2.4.2)
11
11
  builder (3.2.4)
12
12
  childprocess (4.1.0)
13
- codecov (0.6.0)
14
- simplecov (>= 0.15, < 0.22)
13
+ codecov (0.2.12)
14
+ json
15
+ simplecov
15
16
  docile (1.4.0)
16
17
  iniparse (1.5.0)
17
- minitest (5.15.0)
18
+ json (2.6.3)
19
+ minitest (5.16.3)
18
20
  minitest-reporters (1.5.0)
19
21
  ansi
20
22
  builder
21
23
  minitest (>= 5.0)
22
24
  ruby-progressbar
23
- overcommit (0.58.0)
25
+ overcommit (0.59.1)
24
26
  childprocess (>= 0.6.3, < 5)
25
27
  iniparse (~> 1.4)
26
28
  rexml (~> 3.2)
27
- parallel (1.21.0)
28
- parser (3.1.1.0)
29
+ parallel (1.22.1)
30
+ parser (3.1.3.0)
29
31
  ast (~> 2.4.1)
30
32
  rainbow (3.1.1)
31
33
  rake (13.0.6)
32
- regexp_parser (2.2.1)
34
+ regexp_parser (2.6.1)
33
35
  rexml (3.2.5)
34
- rubocop (1.26.0)
36
+ rubocop (1.41.1)
37
+ json (~> 2.3)
35
38
  parallel (~> 1.10)
36
- parser (>= 3.1.0.0)
39
+ parser (>= 3.1.2.1)
37
40
  rainbow (>= 2.2.2, < 4.0)
38
41
  regexp_parser (>= 1.8, < 3.0)
39
- rexml
40
- rubocop-ast (>= 1.16.0, < 2.0)
42
+ rexml (>= 3.2.5, < 4.0)
43
+ rubocop-ast (>= 1.23.0, < 2.0)
41
44
  ruby-progressbar (~> 1.7)
42
45
  unicode-display_width (>= 1.4.0, < 3.0)
43
- rubocop-ast (1.16.0)
46
+ rubocop-ast (1.24.0)
44
47
  parser (>= 3.1.1.0)
45
- rubocop-minitest (0.18.0)
48
+ rubocop-minitest (0.25.1)
46
49
  rubocop (>= 0.90, < 2.0)
47
- rubocop-packaging (0.5.1)
48
- rubocop (>= 0.89, < 2.0)
49
- rubocop-performance (1.13.3)
50
+ rubocop-packaging (0.5.2)
51
+ rubocop (>= 1.33, < 2.0)
52
+ rubocop-performance (1.15.2)
50
53
  rubocop (>= 1.7.0, < 2.0)
51
54
  rubocop-ast (>= 0.4.0)
52
55
  ruby-progressbar (1.11.0)
53
- simplecov (0.21.2)
56
+ simplecov (0.22.0)
54
57
  docile (~> 1.1)
55
58
  simplecov-html (~> 0.11)
56
59
  simplecov_json_formatter (~> 0.1)
57
60
  simplecov-html (0.12.3)
58
- simplecov_json_formatter (0.1.3)
59
- unicode-display_width (2.1.0)
61
+ simplecov_json_formatter (0.1.4)
62
+ unicode-display_width (2.3.0)
60
63
 
61
64
  PLATFORMS
62
65
  ruby
@@ -75,4 +78,4 @@ DEPENDENCIES
75
78
  simplecov (>= 0.21.2)
76
79
 
77
80
  BUNDLED WITH
78
- 2.1.4
81
+ 2.2.32
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Grift
2
2
 
3
3
  [![gem version](https://badge.fury.io/rb/grift.svg)](https://rubygems.org/gems/grift)
4
- [![downloads](https://ruby-gem-downloads-badge.herokuapp.com/grift)](https://rubygems.org/gems/grift)
5
4
  [![build](https://github.com/clarkedb/grift/actions/workflows/ci.yml/badge.svg)](https://github.com/clarkedb/grift/actions?query=workflow%3ACI)
6
5
  [![codecov](https://codecov.io/gh/clarkedb/grift/branch/main/graph/badge.svg)](https://codecov.io/gh/clarkedb/grift)
7
6
 
@@ -86,6 +85,7 @@ my_spy.mock_implementation do |arg1, arg2, **kwargs|
86
85
  x = do_something(arg1, arg2, kwargs[:arg3], kwargs[:arg4])
87
86
  do_something_else(x) # the last line will be returned
88
87
  end
88
+ ```
89
89
 
90
90
  ### Chaining
91
91
 
@@ -122,7 +122,7 @@ my_mock.mock.results
122
122
 
123
123
  ## Requirements
124
124
 
125
- Grift supports all Ruby versions >= 2.7 (including 3.1).
125
+ Grift supports all Ruby versions >= 2.7 (including 3.2).
126
126
 
127
127
  ## Development
128
128
 
@@ -134,6 +134,8 @@ module Grift
134
134
  # @return [self] the mock itself
135
135
  #
136
136
  def mock_implementation(*)
137
+ raise(Grift::Error, 'Must provide a block for the new implementation') unless block_given?
138
+
137
139
  premock_setup
138
140
  mock_executions = @mock_executions # required to access inside class instance block
139
141
 
@@ -143,7 +145,116 @@ module Grift
143
145
 
144
146
  # record the args passed in the call to the method and the result
145
147
  mock_executions.store(args: args, result: return_value)
146
- return return_value
148
+ return_value
149
+ end
150
+ class_instance.send(@method_access, @method_name)
151
+
152
+ self
153
+ end
154
+
155
+ ##
156
+ # Accepts a block and mocks the method to execute that block instead
157
+ # of the original behavior the next time the method is called while mocked.
158
+ # After the method has been called once, it will return to its original
159
+ # behavior. The method will continue to be watched.
160
+ #
161
+ # @see #mock_implementation
162
+ #
163
+ # @example
164
+ # my_mock = Grift.spy_on(String, :downcase).mock_implementation_once do
165
+ # x = 3 + 4
166
+ # x.to_s
167
+ # end
168
+ # ["Banana", "Apple"].map(&:downcase)
169
+ # #=> ["7", "apple"]
170
+ #
171
+ # @return [self] the mock itself
172
+ #
173
+ def mock_implementation_once(*)
174
+ raise(Grift::Error, 'Must provide a block for the new implementation') unless block_given?
175
+
176
+ premock_setup
177
+
178
+ # required to access inside class instance block
179
+ mock_executions = @mock_executions
180
+ clean_mock = lambda do
181
+ unmock_method
182
+ watch_method
183
+ end
184
+
185
+ class_instance.remove_method(@method_name) if !@inherited && method_defined?
186
+ class_instance.define_method @method_name do |*args, **kwargs|
187
+ return_value = yield(*args, **kwargs)
188
+
189
+ # record the args passed in the call to the method and the result
190
+ mock_executions.store(args: args, result: return_value)
191
+
192
+ clean_mock.call
193
+
194
+ return_value
195
+ end
196
+ class_instance.send(@method_access, @method_name)
197
+
198
+ self
199
+ end
200
+
201
+ ##
202
+ # Accepts a number +n+ and a block and mocks the method to execute that block
203
+ # instaead of the original behavior the next +n+ times the method is called
204
+ # while mocked. After the method has been called once, it will return to its
205
+ # original behavior. The method will continue to be watched.
206
+ #
207
+ # **IMPORANT:** Calling {#mock_clear} clears the method call history. If it is
208
+ # called before the nth execution of the mocked method, the method will remain
209
+ # mocked for an additonal +n+ calls.
210
+ #
211
+ # @see #mock_implementation
212
+ #
213
+ # @example
214
+ # my_mock = Grift.spy_on(String, :downcase).mock_implementation_n_times(3) do
215
+ # x = 3 + 4
216
+ # x.to_s
217
+ # end
218
+ # ["Banana", "Apple", "Orange", "Guava"].map(&:downcase)
219
+ # #=> ["7", "7", "7", "guava"]
220
+ #
221
+ # @example
222
+ # my_mock = Grift.spy_on(String, :downcase).mock_implementation_n_times(5) do
223
+ # x = 3 + 4
224
+ # x.to_s
225
+ # end
226
+ # ["Banana", "Apple", "Orange", "Guava"].map(&:downcase)
227
+ # #=> ["7", "7", "7", "7"]
228
+ # my_mock.mock_clear # clear mock history before 5th (nth) method call
229
+ # ["Banana", "Apple", "Orange", "Guava"].map(&:downcase)
230
+ # #=> ["7", "7", "7", "7"]
231
+ #
232
+ # @param n [Number] the number of times to mock the implementation
233
+ #
234
+ # @return [self] the mock itself
235
+ #
236
+ def mock_implementation_n_times(n, *)
237
+ raise(Grift::Error, 'Must provide a block for the new implementation') unless block_given?
238
+
239
+ premock_setup
240
+
241
+ # required to access inside class instance block
242
+ mock_executions = @mock_executions
243
+ clean_mock = lambda do
244
+ unmock_method
245
+ watch_method
246
+ end
247
+
248
+ class_instance.remove_method(@method_name) if !@inherited && method_defined?
249
+ class_instance.define_method @method_name do |*args, **kwargs|
250
+ return_value = yield(*args, **kwargs)
251
+
252
+ # record the args passed in the call to the method and the result
253
+ mock_executions.store(args: args, result: return_value)
254
+
255
+ clean_mock.call if mock_executions.count == n
256
+
257
+ return_value
147
258
  end
148
259
  class_instance.send(@method_access, @method_name)
149
260
 
@@ -173,7 +284,144 @@ module Grift
173
284
  class_instance.define_method @method_name do |*args, **kwargs|
174
285
  # record the args passed in the call to the method and the result
175
286
  mock_executions.store(args: args, kwargs: kwargs, result: return_value)
176
- return return_value
287
+ return_value
288
+ end
289
+ class_instance.send(@method_access, @method_name)
290
+
291
+ self
292
+ end
293
+
294
+ ##
295
+ # Accepts a value and mocks the method to return that value once instead
296
+ # of executing its original behavior while mocked. After the method has
297
+ # been called once, it will return to its original behavior. The method
298
+ # will continue to be watched.
299
+ #
300
+ # @example
301
+ # my_mock = Grift.spy_on(String, :upcase).mock_return_value_once("BANANA")
302
+ # ["apple", "apple"].map(&:upcase)
303
+ # #=> ["BANANA", "APPLE"]
304
+ #
305
+ # @param return_value the value to return from the method once
306
+ #
307
+ # @return [self] the mock itself
308
+ #
309
+ def mock_return_value_once(return_value = nil)
310
+ premock_setup
311
+
312
+ # required to access mock inside class instance block
313
+ mock_executions = @mock_executions
314
+ clean_mock = lambda do
315
+ unmock_method
316
+ watch_method
317
+ end
318
+
319
+ class_instance.remove_method(@method_name) if !@inherited && method_defined?
320
+ class_instance.define_method @method_name do |*args, **kwargs|
321
+ # record the args passed in the call to the method and the result
322
+ mock_executions.store(args: args, kwargs: kwargs, result: return_value)
323
+
324
+ clean_mock.call
325
+
326
+ return_value
327
+ end
328
+ class_instance.send(@method_access, @method_name)
329
+
330
+ self
331
+ end
332
+
333
+ ##
334
+ # Accepts a value and mocks the method to return that value +n+ times instead
335
+ # of executing its original behavior while mocked. After the method has
336
+ # been called +n+ times, it will return to its original behavior. The method
337
+ # will continue to be watched.
338
+ #
339
+ # **IMPORANT:** Calling {#mock_clear} clears the method call history. If it is
340
+ # called before the nth execution of the mocked method, the method will remain
341
+ # mocked for an additional +n+ calls.
342
+ #
343
+ # @example
344
+ # my_mock = Grift.spy_on(String, :upcase).mock_return_value_n_times(2, "BANANA")
345
+ # ["apple", "apple", "apple"].map(&:upcase)
346
+ # #=> ["BANANA", "BANANA", "APPLE"]
347
+ #
348
+ # @example
349
+ # my_mock = Grift.spy_on(String, :upcase).mock_return_value_n_times(4, "BANANA")
350
+ # ["apple", "apple", "apple"].map(&:upcase)
351
+ # #=> ["BANANA", "BANANA", "BANANA"]
352
+ # my_mock.mock_clear # clear mock history before 4th (nth) method call
353
+ # ["apple", "apple", "apple"].map(&:upcase)
354
+ # #=> ["BANANA", "BANANA", "BANANA"]
355
+ #
356
+ # @param n [Number] the number of times to mock the return value
357
+ # @param return_value the value to return from the method +n+ times
358
+ #
359
+ # @return [self] the mock itself
360
+ #
361
+ def mock_return_value_n_times(n, return_value = nil)
362
+ premock_setup
363
+
364
+ # required to access mock inside class instance block
365
+ mock_executions = @mock_executions
366
+ clean_mock = lambda do
367
+ unmock_method
368
+ watch_method
369
+ end
370
+
371
+ class_instance.remove_method(@method_name) if !@inherited && method_defined?
372
+ class_instance.define_method @method_name do |*args, **kwargs|
373
+ # record the args passed in the call to the method and the result
374
+ mock_executions.store(args: args, kwargs: kwargs, result: return_value)
375
+
376
+ clean_mock.call if mock_executions.count == n
377
+
378
+ return_value
379
+ end
380
+ class_instance.send(@method_access, @method_name)
381
+
382
+ self
383
+ end
384
+
385
+ ##
386
+ # Accepts an array of values and mocks the method to return those values
387
+ # in order instead of executing its original behavior while mocked. After
388
+ # the method has been called enough times to return each of the values,
389
+ # it will return to its original behavior. The method continue to be watched.
390
+ #
391
+ # @example
392
+ # mock_values = ["APPLE", "BANANA", "ORANGE"]
393
+ # my_mock = Grift.spy_on(String, :upcase).mock_return_values_in_order(mock_values)
394
+ # ["pineapple", "orange", "guava", "mango", "watermelon"].map(&:upcase)
395
+ # #=> ["APPLE", "BANANA", "ORANGE", "MANGO", "WATERMELON"]
396
+ #
397
+ # @param return_values [Array] the values to return from the method in order
398
+ #
399
+ # @return [self] the mock itself
400
+ #
401
+ def mock_return_values_in_order(return_values)
402
+ unless return_values.is_a?(Array) && !return_values.empty?
403
+ raise(Grift::Error, 'Must provide a non-empty array for the return values')
404
+ end
405
+
406
+ premock_setup
407
+
408
+ # required to access mock inside class instance block
409
+ mock_executions = @mock_executions
410
+ clean_mock = lambda do
411
+ unmock_method
412
+ watch_method
413
+ end
414
+ return_values_internal = return_values.dup
415
+
416
+ class_instance.remove_method(@method_name) if !@inherited && method_defined?
417
+ class_instance.define_method @method_name do |*args, **kwargs|
418
+ # record the args passed in the call to the method and the result
419
+ return_value = return_values_internal.shift
420
+ mock_executions.store(args: args, kwargs: kwargs, result: return_value)
421
+
422
+ clean_mock.call if return_values_internal.empty?
423
+
424
+ return_value
177
425
  end
178
426
  class_instance.send(@method_access, @method_name)
179
427
 
@@ -204,7 +452,7 @@ module Grift
204
452
  # @return [String] the hash of the class and method
205
453
  #
206
454
  def self.hash_key(klass, method_name)
207
- "#{klass}\##{method_name}"
455
+ "#{klass}##{method_name}"
208
456
  end
209
457
 
210
458
  private
@@ -220,12 +468,12 @@ module Grift
220
468
  cache_method_name = @cache_method_name
221
469
 
222
470
  class_instance.remove_method(@method_name) if !@inherited && method_defined?
223
- class_instance.define_method @method_name do |*args, **kwargs|
224
- return_value = send(cache_method_name, *args, **kwargs)
471
+ class_instance.define_method @method_name do |*args, **kwargs, &block|
472
+ return_value = send(cache_method_name, *args, **kwargs, &block)
225
473
 
226
474
  # record the args passed in the call to the method and the result
227
475
  mock_executions.store(args: args, kwargs: kwargs, result: return_value)
228
- return return_value
476
+ return_value
229
477
  end
230
478
  class_instance.send(@method_access, @method_name)
231
479
 
data/lib/grift/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Grift
4
4
  # gem version
5
- VERSION = '2.0.0'
5
+ VERSION = '2.1.0'
6
6
  public_constant :VERSION
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grift
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clark Brown
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-15 00:00:00.000000000 Z
11
+ date: 2022-12-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A gem for simple mocking and spying in Ruby's MiniTest framework.
14
14
  email:
@@ -69,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
69
  - !ruby/object:Gem::Version
70
70
  version: '0'
71
71
  requirements: []
72
- rubygems_version: 3.1.6
72
+ rubygems_version: 3.3.26
73
73
  signing_key:
74
74
  specification_version: 4
75
75
  summary: Mocking and spying in MiniTest