shoulda-matchers 6.4.0 → 6.5.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: 0a3a788cc37a73b615004a4e88dacb051413e00f9a752b78f00cfa8b529e4149
4
- data.tar.gz: 99e5319049435d5ae107d068cecacd547bbd5135e1f1966bf8e15c40e5cde84f
3
+ metadata.gz: 28aa81eb37b239f9f7715725027b774736992c82c2f6905b0d244a35f9db080d
4
+ data.tar.gz: 2433121dc12d086b12c19f5313abcaf8c885cd03de594dd3bcaae0f9150c4173
5
5
  SHA512:
6
- metadata.gz: 5f2b4708f133c2e0a790c99de4608d976d8dfe8704436adeea539e816a18bc6bca7612e7ff669bc4830f8f617550a0db7a70574f611a791dc27879d66f6ed09c
7
- data.tar.gz: 460ac28a8c3833b30ec3171f62a5d5225712132837caeb1c620df49cdcc1fc2d92614a8a652ac93f7707b06d4fe24c5c9ee70fc9759b8b34a3929f845931a2b5
6
+ metadata.gz: c3dab42effbda8285e424feab81c543bb2e84d9764b513db8007a6b6bb0a2dd923fc76db8f253013dc701588dda5ca314fa3d7e2de9e15af84a8a84f9245670b
7
+ data.tar.gz: 6902d75fa7b45c3adc8c97917e9fc1c610be1e99a95bb860ba5112793cdb7653189e4f303f8c879842576a679e3975a520d8ff98dc160ad2168b3fa0b105ecf5
data/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  [logo]: https://matchers.shoulda.io/images/shoulda-matchers-logo.png
14
14
  [website]: https://matchers.shoulda.io/
15
15
 
16
- Shoulda Matchers provides RSpec- and Minitest-compatible one-liners to test
16
+ Shoulda Matchers provides RSpec and Minitest-compatible one-liners to test
17
17
  common Rails functionality that, if written by hand, would be much longer, more
18
18
  complex, and error-prone.
19
19
 
@@ -27,25 +27,33 @@ complex, and error-prone.
27
27
 
28
28
  ## Table of contents
29
29
 
30
- * [Getting started](#getting-started)
31
- * [RSpec](#rspec)
32
- * [Minitest](#minitest)
33
- * [Usage](#usage)
34
- * [On the subject of `subject`](#on-the-subject-of-subject)
35
- * [Availability of RSpec matchers in example groups](#availability-of-rspec-matchers-in-example-groups)
36
- * [`should` vs `is_expected.to`](#should-vs-is_expectedto)
37
- * [Matchers](#matchers)
38
- * [ActiveModel matchers](#activemodel-matchers)
39
- * [ActiveRecord matchers](#activerecord-matchers)
40
- * [ActionController matchers](#actioncontroller-matchers)
41
- * [Independent matchers](#independent-matchers)
42
- * [Extensions](#extensions)
43
- * [Contributing](#contributing)
44
- * [Compatibility](#compatibility)
45
- * [Versioning](#versioning)
46
- * [Team](#team)
47
- * [Copyright/License](#copyright-license)
48
- * [About thoughtbot](#about-thoughtbot)
30
+ - [Getting started](#getting-started)
31
+ - [RSpec](#rspec)
32
+ - [Rails apps](#rails-apps)
33
+ - [Non-Rails apps](#non-rails-apps)
34
+ - [Minitest](#minitest)
35
+ - [Rails apps](#rails-apps-1)
36
+ - [Non-Rails apps](#non-rails-apps-1)
37
+ - [Usage](#usage)
38
+ - [On the subject of `subject`](#on-the-subject-of-subject)
39
+ - [Availability of RSpec matchers in example groups](#availability-of-rspec-matchers-in-example-groups)
40
+ - [Rails projects](#rails-projects)
41
+ - [Non-Rails projects](#non-rails-projects)
42
+ - [`should` vs `is_expected.to`](#should-vs-is_expectedto)
43
+ - [A Note on Testing Style](#a-note-on-testing-style)
44
+ - [Matchers](#matchers)
45
+ - [ActiveModel matchers](#activemodel-matchers)
46
+ - [ActiveRecord matchers](#activerecord-matchers)
47
+ - [ActionController matchers](#actioncontroller-matchers)
48
+ - [Routing matchers](#routing-matchers)
49
+ - [Independent matchers](#independent-matchers)
50
+ - [Extensions](#extensions)
51
+ - [Contributing](#contributing)
52
+ - [Compatibility](#compatibility)
53
+ - [Versioning](#versioning)
54
+ - [Team](#team)
55
+ - [Copyright/License](#copyrightlicense)
56
+ - [About thoughtbot](#about-thoughtbot)
49
57
 
50
58
  ## Getting started
51
59
 
@@ -63,8 +71,8 @@ Then run `bundle install`.
63
71
 
64
72
  Now you need to configure the gem by telling it:
65
73
 
66
- * which matchers you want to use in your tests
67
- * that you're using RSpec so that it can make those matchers available in
74
+ - which matchers you want to use in your tests
75
+ - that you're using RSpec so that it can make those matchers available in
68
76
  your example groups
69
77
 
70
78
  #### Rails apps
@@ -125,8 +133,8 @@ Then run `bundle install`.
125
133
 
126
134
  Now you need to configure the gem by telling it:
127
135
 
128
- * which matchers you want to use in your tests
129
- * that you're using Minitest so that it can make those matchers available in
136
+ - which matchers you want to use in your tests
137
+ - that you're using Minitest so that it can make those matchers available in
130
138
  your test case classes
131
139
 
132
140
  #### Rails apps
@@ -167,18 +175,18 @@ end
167
175
  Most of the matchers provided by this gem are useful in a Rails context, and as
168
176
  such, can be used for different parts of a Rails app:
169
177
 
170
- * [database models backed by ActiveRecord](#activerecord-matchers)
171
- * [non-database models, form objects, etc. backed by
178
+ - [database models backed by ActiveRecord](#activerecord-matchers)
179
+ - [non-database models, form objects, etc. backed by
172
180
  ActiveModel](#activemodel-matchers)
173
- * [controllers](#actioncontroller-matchers)
174
- * [routes](#routing-matchers) (RSpec only)
175
- * [Rails-specific features like `delegate`](#independent-matchers)
181
+ - [controllers](#actioncontroller-matchers)
182
+ - [routes](#routing-matchers) (RSpec only)
183
+ - [Rails-specific features like `delegate`](#independent-matchers)
176
184
 
177
185
  As the name of the gem indicates, most matchers are designed to be used in
178
186
  "one-liner" form using the `should` macro, a special directive available in both
179
187
  RSpec and [Shoulda]. For instance, a model test case may look something like:
180
188
 
181
- ``` ruby
189
+ ```ruby
182
190
  # RSpec
183
191
  RSpec.describe MenuItem, type: :model do
184
192
  describe 'associations' do
@@ -216,7 +224,7 @@ in certain cases it can be advantageous to override it. For instance, when
216
224
  testing validations in a model, it is customary to provide a valid model instead
217
225
  of a fresh one:
218
226
 
219
- ``` ruby
227
+ ```ruby
220
228
  # RSpec
221
229
  RSpec.describe Post, type: :model do
222
230
  describe 'validations' do
@@ -242,7 +250,7 @@ correct object. **When in doubt, provide an instance of the class under test.**
242
250
  This is particularly necessary for controller tests, where it is easy to
243
251
  accidentally write something like:
244
252
 
245
- ``` ruby
253
+ ```ruby
246
254
  RSpec.describe PostsController, type: :controller do
247
255
  describe 'GET #index' do
248
256
  subject { get :index }
@@ -257,7 +265,7 @@ end
257
265
 
258
266
  In this case, you would want to use `before` rather than `subject`:
259
267
 
260
- ``` ruby
268
+ ```ruby
261
269
  RSpec.describe PostsController, type: :controller do
262
270
  describe 'GET #index' do
263
271
  before { get :index }
@@ -275,26 +283,26 @@ end
275
283
  #### Rails projects
276
284
 
277
285
  If you're using RSpec, then you're probably familiar with the concept of example
278
- groups. Example groups can be assigned tags order to assign different behavior
286
+ groups. Example groups can be assigned tags to assign different behaviors
279
287
  to different kinds of example groups. This comes into play especially when using
280
288
  `rspec-rails`, where, for instance, controller example groups, tagged with
281
289
  `type: :controller`, are written differently than request example groups, tagged
282
290
  with `type: :request`. This difference in writing style arises because
283
- `rspec-rails` mixes different behavior and methods into controller example
291
+ `rspec-rails` mixes different behaviors and methods into controller example
284
292
  groups vs. request example groups.
285
293
 
286
294
  Relying on this behavior, Shoulda Matchers automatically makes certain matchers
287
295
  available in certain kinds of example groups:
288
296
 
289
- * ActiveRecord and ActiveModel matchers are available only in model example
297
+ - ActiveRecord and ActiveModel matchers are available only in model example
290
298
  groups, i.e., those tagged with `type: :model` or in files located under
291
299
  `spec/models`.
292
- * ActionController matchers are available only in controller example groups,
300
+ - ActionController matchers are available only in controller example groups,
293
301
  i.e., those tagged with `type: :controller` or in files located under
294
302
  `spec/controllers`.
295
- * The `route` matcher is available in routing example groups, i.e., those
303
+ - The `route` matcher is available in routing example groups, i.e., those
296
304
  tagged with `type: :routing` or in files located under `spec/routing`.
297
- * Independent matchers are available in all example groups.
305
+ - Independent matchers are available in all example groups.
298
306
 
299
307
  As long as you're using Rails, you don't need to worry about these details —
300
308
  everything should "just work".
@@ -306,7 +314,7 @@ and you want to use model matchers in a certain example group?** Then you'll
306
314
  need to manually include the module that holds those matchers into that example
307
315
  group. For instance, you might have to say:
308
316
 
309
- ``` ruby
317
+ ```ruby
310
318
  RSpec.describe MySpecialModel do
311
319
  include Shoulda::Matchers::ActiveModel
312
320
  include Shoulda::Matchers::ActiveRecord
@@ -336,7 +344,7 @@ end
336
344
  ### `should` vs `is_expected.to`
337
345
 
338
346
  In this README and throughout the documentation, you'll notice that we use the
339
- `should` form of RSpec's one-liner syntax over `is_expected.to`. Beside being
347
+ `should` form of RSpec's one-liner syntax over `is_expected.to`. Besides being
340
348
  the namesake of the gem itself, this is our preferred syntax as it's short and
341
349
  sweet. But if you prefer to use `is_expected.to`, you can do that too:
342
350
 
@@ -346,6 +354,25 @@ RSpec.describe Person, type: :model do
346
354
  end
347
355
  ```
348
356
 
357
+ ### A Note on Testing Style
358
+
359
+ If you inspect the source code, you'll notice quickly that `shoulda-matchers`
360
+ is largely implemented using reflections and other introspection methods that
361
+ Rails provides. At first sight, this might seem to go against the common
362
+ practice of testing behavior rather than implementation. However, as the
363
+ available matchers indicate, we recommend that you treat `shoulda-matchers` as
364
+ a tool to help you ensure correct configuration and adherence to best practices
365
+ and idiomatic Rails in your models and controllers - especially for aspects
366
+ that in your experience are often insufficiently tested, such as ActiveRecord
367
+ validations or controller callbacks (a.k.a. the "framework-y" parts).
368
+
369
+ For testing your application's unique business logic, however, we recommend focusing on
370
+ behavior and outcomes over implementation details. This approach will better support
371
+ refactoring and ensure that your tests remain resilient to changes in how your code
372
+ is structured. While no generalized testing tool can fully capture the nuances of your
373
+ specific domain, you can draw inspiration from shoulda-matchers to write custom
374
+ matchers that align more closely with your application's needs.
375
+
349
376
  ## Matchers
350
377
 
351
378
  Here is the full list of matchers that ship with this gem. If you need details
@@ -353,106 +380,106 @@ about any of them, make sure to [consult the documentation][rubydocs]!
353
380
 
354
381
  ### ActiveModel matchers
355
382
 
356
- * **[allow_value](lib/shoulda/matchers/active_model/allow_value_matcher.rb)**
383
+ - **[allow_value](lib/shoulda/matchers/active_model/allow_value_matcher.rb)**
357
384
  tests that an attribute is valid or invalid if set to one or more values.
358
- *(Aliased as #allow_values.)*
359
- * **[have_secure_password](lib/shoulda/matchers/active_model/have_secure_password_matcher.rb)**
385
+ _(Aliased as #allow_values.)_
386
+ - **[have_secure_password](lib/shoulda/matchers/active_model/have_secure_password_matcher.rb)**
360
387
  tests usage of `has_secure_password`.
361
- * **[validate_absence_of](lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb)**
388
+ - **[validate_absence_of](lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb)**
362
389
  tests usage of `validates_absence_of`.
363
- * **[validate_acceptance_of](lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb)**
390
+ - **[validate_acceptance_of](lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb)**
364
391
  tests usage of `validates_acceptance_of`.
365
- * **[validate_confirmation_of](lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb)**
392
+ - **[validate_confirmation_of](lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb)**
366
393
  tests usage of `validates_confirmation_of`.
367
- * **[validate_exclusion_of](lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb)**
394
+ - **[validate_exclusion_of](lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb)**
368
395
  tests usage of `validates_exclusion_of`.
369
- * **[validate_inclusion_of](lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb)**
396
+ - **[validate_inclusion_of](lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb)**
370
397
  tests usage of `validates_inclusion_of`.
371
- * **[validate_length_of](lib/shoulda/matchers/active_model/validate_length_of_matcher.rb)**
398
+ - **[validate_length_of](lib/shoulda/matchers/active_model/validate_length_of_matcher.rb)**
372
399
  tests usage of `validates_length_of`.
373
- * **[validate_numericality_of](lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb)**
400
+ - **[validate_numericality_of](lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb)**
374
401
  tests usage of `validates_numericality_of`.
375
- * **[validate_presence_of](lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb)**
402
+ - **[validate_presence_of](lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb)**
376
403
  tests usage of `validates_presence_of`.
377
- * **[validate_comparison_of](lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb)**
404
+ - **[validate_comparison_of](lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb)**
378
405
  tests usage of `validates_comparison_of`.
379
406
 
380
407
  ### ActiveRecord matchers
381
408
 
382
- * **[accept_nested_attributes_for](lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb)**
409
+ - **[accept_nested_attributes_for](lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb)**
383
410
  tests usage of the `accepts_nested_attributes_for` macro.
384
- * **[belong_to](lib/shoulda/matchers/active_record/association_matcher.rb)**
411
+ - **[belong_to](lib/shoulda/matchers/active_record/association_matcher.rb)**
385
412
  tests your `belongs_to` associations.
386
- * **[define_enum_for](lib/shoulda/matchers/active_record/define_enum_for_matcher.rb)**
413
+ - **[define_enum_for](lib/shoulda/matchers/active_record/define_enum_for_matcher.rb)**
387
414
  tests usage of the `enum` macro.
388
- * **[have_and_belong_to_many](lib/shoulda/matchers/active_record/association_matcher.rb)**
415
+ - **[have_and_belong_to_many](lib/shoulda/matchers/active_record/association_matcher.rb)**
389
416
  tests your `has_and_belongs_to_many` associations.
390
- * **[have_delegated_type](lib/shoulda/matchers/active_record/association_matcher.rb#L687)**
417
+ - **[have_delegated_type](lib/shoulda/matchers/active_record/association_matcher.rb#L687)**
391
418
  tests usage of the `delegated_type` macro.
392
- * **[have_db_column](lib/shoulda/matchers/active_record/have_db_column_matcher.rb)**
419
+ - **[have_db_column](lib/shoulda/matchers/active_record/have_db_column_matcher.rb)**
393
420
  tests that the table that backs your model has a specific column.
394
- * **[have_db_index](lib/shoulda/matchers/active_record/have_db_index_matcher.rb)**
421
+ - **[have_db_index](lib/shoulda/matchers/active_record/have_db_index_matcher.rb)**
395
422
  tests that the table that backs your model has an index on a specific column.
396
- * **[have_implicit_order_column](lib/shoulda/matchers/active_record/have_implicit_order_column.rb)**
423
+ - **[have_implicit_order_column](lib/shoulda/matchers/active_record/have_implicit_order_column.rb)**
397
424
  tests usage of `implicit_order_column`.
398
- * **[have_many](lib/shoulda/matchers/active_record/association_matcher.rb#L328)**
425
+ - **[have_many](lib/shoulda/matchers/active_record/association_matcher.rb#L328)**
399
426
  tests your `has_many` associations.
400
- * **[have_many_attached](lib/shoulda/matchers/active_record/have_attached_matcher.rb)**
427
+ - **[have_many_attached](lib/shoulda/matchers/active_record/have_attached_matcher.rb)**
401
428
  tests your `has_many_attached` associations.
402
- * **[have_one](lib/shoulda/matchers/active_record/association_matcher.rb#L598)**
429
+ - **[have_one](lib/shoulda/matchers/active_record/association_matcher.rb#L598)**
403
430
  tests your `has_one` associations.
404
- * **[have_one_attached](lib/shoulda/matchers/active_record/have_attached_matcher.rb)**
431
+ - **[have_one_attached](lib/shoulda/matchers/active_record/have_attached_matcher.rb)**
405
432
  tests your `has_one_attached` associations.
406
- * **[have_readonly_attribute](lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb)**
433
+ - **[have_readonly_attribute](lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb)**
407
434
  tests usage of the `attr_readonly` macro.
408
- * **[have_rich_text](lib/shoulda/matchers/active_record/have_rich_text_matcher.rb)**
435
+ - **[have_rich_text](lib/shoulda/matchers/active_record/have_rich_text_matcher.rb)**
409
436
  tests your `has_rich_text` associations.
410
- * **[serialize](lib/shoulda/matchers/active_record/serialize_matcher.rb)** tests
437
+ - **[serialize](lib/shoulda/matchers/active_record/serialize_matcher.rb)** tests
411
438
  usage of the `serialize` macro.
412
- * **[validate_uniqueness_of](lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb)**
439
+ - **[validate_uniqueness_of](lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb)**
413
440
  tests usage of `validates_uniqueness_of`.
414
- * **[normalize](lib/shoulda/matchers/active_record/normalize_matcher.rb)** tests
441
+ - **[normalize](lib/shoulda/matchers/active_record/normalize_matcher.rb)** tests
415
442
  usage of the `normalize` macro
416
- * **[encrypt](lib/shoulda/matchers/active_record/encrypt_matcher.rb)**
443
+ - **[encrypt](lib/shoulda/matchers/active_record/encrypt_matcher.rb)**
417
444
  tests usage of the `encrypts` macro.
418
445
 
419
446
  ### ActionController matchers
420
447
 
421
- * **[filter_param](lib/shoulda/matchers/action_controller/filter_param_matcher.rb)**
448
+ - **[filter_param](lib/shoulda/matchers/action_controller/filter_param_matcher.rb)**
422
449
  tests parameter filtering configuration.
423
- * **[permit](lib/shoulda/matchers/action_controller/permit_matcher.rb)** tests
424
- that an action places a restriction on the `params` hash.
425
- * **[redirect_to](lib/shoulda/matchers/action_controller/redirect_to_matcher.rb)**
450
+ - **[permit](lib/shoulda/matchers/action_controller/permit_matcher.rb)** tests
451
+ that an action restricts the `params` hash.
452
+ - **[redirect_to](lib/shoulda/matchers/action_controller/redirect_to_matcher.rb)**
426
453
  tests that an action redirects to a certain location.
427
- * **[render_template](lib/shoulda/matchers/action_controller/render_template_matcher.rb)**
454
+ - **[render_template](lib/shoulda/matchers/action_controller/render_template_matcher.rb)**
428
455
  tests that an action renders a template.
429
- * **[render_with_layout](lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb)**
456
+ - **[render_with_layout](lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb)**
430
457
  tests that an action is rendered with a certain layout.
431
- * **[rescue_from](lib/shoulda/matchers/action_controller/rescue_from_matcher.rb)**
458
+ - **[rescue_from](lib/shoulda/matchers/action_controller/rescue_from_matcher.rb)**
432
459
  tests usage of the `rescue_from` macro.
433
- * **[respond_with](lib/shoulda/matchers/action_controller/respond_with_matcher.rb)**
460
+ - **[respond_with](lib/shoulda/matchers/action_controller/respond_with_matcher.rb)**
434
461
  tests that an action responds with a certain status code.
435
- * **[route](lib/shoulda/matchers/action_controller/route_matcher.rb)** tests
462
+ - **[route](lib/shoulda/matchers/action_controller/route_matcher.rb)** tests
436
463
  your routes.
437
- * **[set_session](lib/shoulda/matchers/action_controller/set_session_matcher.rb)**
464
+ - **[set_session](lib/shoulda/matchers/action_controller/set_session_matcher.rb)**
438
465
  makes assertions on the `session` hash.
439
- * **[set_flash](lib/shoulda/matchers/action_controller/set_flash_matcher.rb)**
466
+ - **[set_flash](lib/shoulda/matchers/action_controller/set_flash_matcher.rb)**
440
467
  makes assertions on the `flash` hash.
441
- * **[use_after_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L29)**
468
+ - **[use_after_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L29)**
442
469
  tests that an `after_action` callback is defined in your controller.
443
- * **[use_around_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L75)**
470
+ - **[use_around_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L75)**
444
471
  tests that an `around_action` callback is defined in your controller.
445
- * **[use_before_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L4)**
472
+ - **[use_before_action](lib/shoulda/matchers/action_controller/callback_matcher.rb#L4)**
446
473
  tests that a `before_action` callback is defined in your controller.
447
474
 
448
475
  ### Routing matchers
449
476
 
450
- * **[route](lib/shoulda/matchers/action_controller/route_matcher.rb)** tests
477
+ - **[route](lib/shoulda/matchers/action_controller/route_matcher.rb)** tests
451
478
  your routes.
452
479
 
453
480
  ### Independent matchers
454
481
 
455
- * **[delegate_method](lib/shoulda/matchers/independent/delegate_method_matcher.rb)**
482
+ - **[delegate_method](lib/shoulda/matchers/independent/delegate_method_matcher.rb)**
456
483
  tests that an object forwards messages to other, internal objects by way of
457
484
  delegation.
458
485
 
@@ -461,7 +488,7 @@ about any of them, make sure to [consult the documentation][rubydocs]!
461
488
  Over time our community has created extensions to Shoulda Matchers. If you've
462
489
  created something that you want to share, please [let us know][new-issue]!
463
490
 
464
- * **[shoulda-matchers-cucumber]** – Adds support for using Shoulda Matchers in
491
+ - **[shoulda-matchers-cucumber]** – Adds support for using Shoulda Matchers in
465
492
  Cucumber tests.
466
493
 
467
494
  [new-issue]: https://github.com/thoughtbot/shoulda-matchers/issues/new
@@ -512,7 +539,7 @@ Sales][matsales28]. Previous maintainers include [Elliot Winkler][mcmire],
512
539
  ## Copyright/License
513
540
 
514
541
  Shoulda Matchers is copyright © Tammer Saleh and [thoughtbot,
515
- inc][thoughtbot-website]. It is free and opensource software and may be
542
+ inc][thoughtbot-website]. It is free and open-source software and may be
516
543
  redistributed under the terms specified in the [LICENSE](LICENSE) file.
517
544
 
518
545
  [thoughtbot-website]: https://thoughtbot.com
@@ -94,14 +94,14 @@ module Shoulda
94
94
  end
95
95
 
96
96
  def description
97
- description = 'render with '
98
- description <<
99
- if @expected_layout.nil?
100
- 'a layout'
101
- else
102
- "the #{@expected_layout.inspect} layout"
103
- end
104
- description
97
+ String.new('render with ').tap do |string|
98
+ string <<
99
+ if @expected_layout.nil?
100
+ 'a layout'
101
+ else
102
+ "the #{@expected_layout.inspect} layout"
103
+ end
104
+ end
105
105
  end
106
106
 
107
107
  private
@@ -27,7 +27,7 @@ module Shoulda
27
27
 
28
28
  def extract_params_from_string
29
29
  controller, action = args[0].split('#')
30
- params = (args[1] || {}).merge(controller: controller, action: action)
30
+ params = (args[1] || {}).merge!(controller: controller, action: action)
31
31
  normalize_values(params)
32
32
  end
33
33
 
@@ -677,7 +677,7 @@ pass, or do something else entirely.
677
677
  attribute: attribute_to_check_message_against,
678
678
  }
679
679
 
680
- defaults.merge(options[:expected_message_values])
680
+ defaults.merge!(options[:expected_message_values])
681
681
  end
682
682
 
683
683
  def model_name
@@ -358,7 +358,7 @@ EOT
358
358
  description = "validate that :#{@attribute}"
359
359
 
360
360
  description <<
361
- if @array.count > 1
361
+ if @array.length > 1
362
362
  " is either #{inspected_array}"
363
363
  else
364
364
  " is #{inspected_array}"
@@ -59,7 +59,7 @@ module Shoulda
59
59
  when String, Symbol
60
60
  { active: true, column: counter_cache.to_s }
61
61
  when Hash
62
- { active: true, column: nil }.merge(counter_cache)
62
+ { active: true, column: nil }.merge!(counter_cache)
63
63
  else
64
64
  raise ArgumentError, 'Invalid counter_cache option'
65
65
  end
@@ -92,7 +92,7 @@ module Shoulda
92
92
  end
93
93
 
94
94
  def column_label
95
- if missing_columns.count > 1
95
+ if missing_columns.length > 1
96
96
  'columns'
97
97
  else
98
98
  'column'
@@ -758,7 +758,7 @@ module Shoulda
758
758
  def to_hash(value)
759
759
  if value.is_a?(Array)
760
760
  value.each_with_index.inject({}) do |hash, (item, index)|
761
- hash.merge(item.to_s => index)
761
+ hash.merge!(item.to_s => index)
762
762
  end
763
763
  else
764
764
  value.stringify_keys
@@ -258,6 +258,36 @@ module Shoulda
258
258
  #
259
259
  # @return [ValidateUniquenessOfMatcher]
260
260
  #
261
+ # ##### alternatives
262
+ #
263
+ # Use `alternatives` to specify alternative valid values to use
264
+ # for testing uniqueness.
265
+ #
266
+ # class Post < ActiveRecord::Base
267
+ # validates :title, uniqueness: true
268
+ # end
269
+ #
270
+ # # RSpec
271
+ # RSpec.describe Post, type: :model do
272
+ # it do
273
+ # should validate_uniqueness_of(:title).alternatives('Alternative Title')
274
+ # end
275
+ # end
276
+ #
277
+ # # Minitest (Shoulda)
278
+ # class PostTest < ActiveSupport::TestCase
279
+ # should validate_uniqueness_of(:title).alternatives(['Alternative Title', 'Another Title'])
280
+ # end
281
+ #
282
+ # @param values [String, Array<String>]
283
+ # Alternative value(s) to use for testing uniqueness instead of using
284
+ # the `succ` operator on the existing value.
285
+ #
286
+ # @return [ValidateUniquenessOfMatcher]
287
+ #
288
+ # @example
289
+ # it { should validate_uniqueness_of(:title).alternatives('Alternative Title') }
290
+ # it { should validate_uniqueness_of(:title).alternatives(['Title 1', 'Title 2']) }
261
291
  def validate_uniqueness_of(attr)
262
292
  ValidateUniquenessOfMatcher.new(attr)
263
293
  end
@@ -286,6 +316,20 @@ module Shoulda
286
316
  self
287
317
  end
288
318
 
319
+ # @param values [String, Array<String>]
320
+ # Alternative value(s) to use for testing uniqueness instead of using
321
+ # the `succ` operator on the existing value.
322
+ #
323
+ # @return [ValidateUniquenessOfMatcher]
324
+ #
325
+ # @example
326
+ # it { should validate_uniqueness_of(:title).alternatives('Alternative Title') }
327
+ # it { should validate_uniqueness_of(:title).alternatives(['Title 1', 'Title 2']) }
328
+ def alternatives(values)
329
+ @options[:alternatives] = values
330
+ self
331
+ end
332
+
289
333
  def case_insensitive
290
334
  @options[:case_sensitivity_strategy] = :insensitive
291
335
  self
@@ -836,6 +880,9 @@ module Shoulda
836
880
  available_values.keys.last
837
881
  elsif polymorphic_type_attribute?(scope, previous_value)
838
882
  Uniqueness::TestModels.create(previous_value).to_s
883
+ elsif @options[:alternatives] && scope == @attribute
884
+ alternatives = Array.wrap(@options[:alternatives])
885
+ alternatives.first
839
886
  elsif previous_value.respond_to?(:next)
840
887
  previous_value.next
841
888
  elsif previous_value.respond_to?(:to_datetime)
@@ -27,7 +27,7 @@ module Shoulda
27
27
 
28
28
  def calls_by_method_name
29
29
  doubles_by_method_name.inject({}) do |hash, (method_name, double)|
30
- hash.merge method_name => double.calls.map(&:args)
30
+ hash.merge! method_name => double.calls.map(&:args)
31
31
  end
32
32
  end
33
33
 
@@ -168,8 +168,27 @@ module Shoulda
168
168
  # should delegate_method(:plan).to(:subscription).allow_nil
169
169
  # end
170
170
  #
171
- # @return [DelegateMethodMatcher]
171
+ # ##### with_private
172
+ #
173
+ # Use `with_private` if the delegation accounts for the fact that your
174
+ # delegation is private. (This is mostly intended as an analogue to
175
+ # the `private` option that Rails' `delegate` helper takes.)
176
+ #
177
+ # class Account
178
+ # delegate :plan, to: :subscription, private: true
179
+ # end
180
+ #
181
+ # # RSpec
182
+ # describe Account do
183
+ # it { should delegate_method(:plan).to(:subscription).with_private }
184
+ # end
185
+ #
186
+ # # Minitest
187
+ # class PageTest < Minitest::Test
188
+ # should delegate_method(:plan).to(:subscription).with_private
189
+ # end
172
190
  #
191
+ # @return [DelegateMethodMatcher]
173
192
  def delegate_method(delegating_method)
174
193
  DelegateMethodMatcher.new(delegating_method).in_context(self)
175
194
  end
@@ -187,6 +206,7 @@ module Shoulda
187
206
  @delegate_object_reader_method = nil
188
207
  @delegated_arguments = []
189
208
  @expects_to_allow_nil_delegate_object = false
209
+ @expects_private_delegation = false
190
210
  end
191
211
 
192
212
  def in_context(context)
@@ -202,7 +222,8 @@ module Shoulda
202
222
  subject_has_delegating_method? &&
203
223
  subject_has_delegate_object_reader_method? &&
204
224
  subject_delegates_to_delegate_object_correctly? &&
205
- subject_handles_nil_delegate_object?
225
+ subject_handles_nil_delegate_object? &&
226
+ subject_handles_private_delegation?
206
227
  end
207
228
 
208
229
  def description
@@ -210,6 +231,10 @@ module Shoulda
210
231
  "delegate #{formatted_delegating_method_name} to the " +
211
232
  "#{formatted_delegate_object_reader_method_name} object"
212
233
 
234
+ if expects_private_delegation?
235
+ string << ' privately'
236
+ end
237
+
213
238
  if delegated_arguments.any?
214
239
  string << " passing arguments #{delegated_arguments.inspect}"
215
240
  end
@@ -254,6 +279,11 @@ module Shoulda
254
279
  self
255
280
  end
256
281
 
282
+ def with_private
283
+ @expects_private_delegation = true
284
+ self
285
+ end
286
+
257
287
  def build_delegating_method_prefix(prefix)
258
288
  case prefix
259
289
  when true, nil then delegate_object_reader_method
@@ -264,14 +294,19 @@ module Shoulda
264
294
  def failure_message
265
295
  message = "Expected #{class_under_test} to #{description}.\n\n"
266
296
 
267
- if failed_to_allow_nil_delegate_object?
297
+ if failed_to_allow_nil_delegate_object? || failed_to_handle_private_delegation?
268
298
  message << formatted_delegating_method_name(include_module: true)
269
299
  message << ' did delegate to '
270
300
  message << formatted_delegate_object_reader_method_name
301
+ end
302
+
303
+ if failed_to_allow_nil_delegate_object?
271
304
  message << ' when it was non-nil, but it failed to account '
272
305
  message << 'for when '
273
306
  message << formatted_delegate_object_reader_method_name
274
307
  message << ' *was* nil.'
308
+ elsif failed_to_handle_private_delegation?
309
+ message << ", but 'private: true' is missing."
275
310
  else
276
311
  message << 'Method calls sent to '
277
312
  message << formatted_delegate_object_reader_method_name(
@@ -322,6 +357,10 @@ module Shoulda
322
357
  @expects_to_allow_nil_delegate_object
323
358
  end
324
359
 
360
+ def expects_private_delegation?
361
+ @expects_private_delegation
362
+ end
363
+
325
364
  def formatted_delegate_method(options = {})
326
365
  formatted_method_name_for(delegate_method, options)
327
366
  end
@@ -367,7 +406,11 @@ module Shoulda
367
406
  end
368
407
 
369
408
  def subject_has_delegating_method?
370
- subject.respond_to?(delegating_method)
409
+ if expects_private_delegation?
410
+ !subject.respond_to?(delegating_method) && subject.respond_to?(delegating_method, true)
411
+ else
412
+ subject.respond_to?(delegating_method)
413
+ end
371
414
  end
372
415
 
373
416
  def subject_has_delegate_object_reader_method?
@@ -381,7 +424,11 @@ module Shoulda
381
424
  end
382
425
 
383
426
  def subject_delegates_to_delegate_object_correctly?
384
- call_delegating_method_with_delegate_method_returning(delegate_object)
427
+ if expects_private_delegation?
428
+ privately_call_delegating_method_with_delegate_method_returning(delegate_object)
429
+ else
430
+ call_delegating_method_with_delegate_method_returning(delegate_object)
431
+ end
385
432
 
386
433
  if delegated_arguments.any?
387
434
  delegate_object_received_call_with_delegated_arguments?
@@ -411,11 +458,37 @@ module Shoulda
411
458
  end
412
459
  end
413
460
 
461
+ def subject_handles_private_delegation?
462
+ @subject_handled_private_delegation =
463
+ if expects_private_delegation?
464
+ begin
465
+ call_delegating_method_with_delegate_method_returning(delegate_object)
466
+ true
467
+ rescue Module::DelegationError
468
+ false
469
+ rescue NoMethodError => e
470
+ if e.message =~
471
+ /private method `#{delegating_method}' called for/
472
+ true
473
+ else
474
+ raise e
475
+ end
476
+ end
477
+ else
478
+ true
479
+ end
480
+ end
481
+
414
482
  def failed_to_allow_nil_delegate_object?
415
483
  expects_to_allow_nil_delegate_object? &&
416
484
  !@subject_handled_nil_delegate_object
417
485
  end
418
486
 
487
+ def failed_to_handle_private_delegation?
488
+ expects_private_delegation? &&
489
+ !@subject_handled_private_delegation
490
+ end
491
+
419
492
  def call_delegating_method_with_delegate_method_returning(value)
420
493
  register_subject_double_collection_to(value)
421
494
 
@@ -424,6 +497,14 @@ module Shoulda
424
497
  end
425
498
  end
426
499
 
500
+ def privately_call_delegating_method_with_delegate_method_returning(value)
501
+ register_subject_double_collection_to(value)
502
+
503
+ Doublespeak.with_doubles_activated do
504
+ subject.__send__(delegating_method, *delegated_arguments)
505
+ end
506
+ end
507
+
427
508
  def register_subject_double_collection_to(returned_value)
428
509
  double_collection =
429
510
  Doublespeak.double_collection_for(subject.singleton_class)
@@ -60,7 +60,7 @@ module Shoulda
60
60
  attribute_types_for(model).
61
61
  inject({}) do |hash, (attribute_name, attribute_type)|
62
62
  if type_serialized_defined && attribute_type.is_a?(::ActiveRecord::Type::Serialized)
63
- hash.merge(attribute_name => attribute_type.coder)
63
+ hash.merge!(attribute_name => attribute_type.coder)
64
64
  else
65
65
  hash
66
66
  end
@@ -121,7 +121,7 @@ module Shoulda
121
121
  model.columns.inject({}) do |hash, column|
122
122
  key = column.name.to_s
123
123
  value = model.type_for_attribute(column.name)
124
- hash.merge(key => value)
124
+ hash.merge!(key => value)
125
125
  end
126
126
  else
127
127
  raise NotImplementedError
@@ -169,7 +169,7 @@ module Shoulda
169
169
  ]
170
170
  primary_translation_key = default_translation_keys.shift
171
171
  translate_options =
172
- { default: default_translation_keys }.merge(options)
172
+ { default: default_translation_keys }.merge!(options)
173
173
  I18n.translate(primary_translation_key, translate_options)
174
174
  end
175
175
 
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- VERSION = '6.4.0'.freeze
4
+ VERSION = '6.5.0'.freeze
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoulda-matchers
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.4.0
4
+ version: 6.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tammer Saleh
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2024-08-16 00:00:00.000000000 Z
17
+ date: 2025-04-25 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: activesupport