grift 2.0.0 → 2.1.0

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: 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