shoulda-matchers 6.3.1 → 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 +4 -4
- data/README.md +117 -90
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +8 -8
- data/lib/shoulda/matchers/action_controller/route_params.rb +1 -1
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +34 -4
- data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +13 -2
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +47 -0
- data/lib/shoulda/matchers/doublespeak/double_collection.rb +1 -1
- data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +86 -5
- data/lib/shoulda/matchers/rails_shim.rb +3 -3
- data/lib/shoulda/matchers/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28aa81eb37b239f9f7715725027b774736992c82c2f6905b0d244a35f9db080d
|
4
|
+
data.tar.gz: 2433121dc12d086b12c19f5313abcaf8c885cd03de594dd3bcaae0f9150c4173
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
67
|
-
|
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
|
-
|
129
|
-
|
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
|
-
|
171
|
-
|
172
|
-
ActiveModel](#
|
173
|
-
|
174
|
-
|
175
|
-
|
178
|
+
- [database models backed by ActiveRecord](#activerecord-matchers)
|
179
|
+
- [non-database models, form objects, etc. backed by
|
180
|
+
ActiveModel](#activemodel-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
|
-
```
|
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
|
-
```
|
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
|
-
```
|
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
|
-
```
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
```
|
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`.
|
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
|
-
|
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
|
-
|
359
|
-
|
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
|
-
|
388
|
+
- **[validate_absence_of](lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb)**
|
362
389
|
tests usage of `validates_absence_of`.
|
363
|
-
|
390
|
+
- **[validate_acceptance_of](lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb)**
|
364
391
|
tests usage of `validates_acceptance_of`.
|
365
|
-
|
392
|
+
- **[validate_confirmation_of](lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb)**
|
366
393
|
tests usage of `validates_confirmation_of`.
|
367
|
-
|
394
|
+
- **[validate_exclusion_of](lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb)**
|
368
395
|
tests usage of `validates_exclusion_of`.
|
369
|
-
|
396
|
+
- **[validate_inclusion_of](lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb)**
|
370
397
|
tests usage of `validates_inclusion_of`.
|
371
|
-
|
398
|
+
- **[validate_length_of](lib/shoulda/matchers/active_model/validate_length_of_matcher.rb)**
|
372
399
|
tests usage of `validates_length_of`.
|
373
|
-
|
400
|
+
- **[validate_numericality_of](lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb)**
|
374
401
|
tests usage of `validates_numericality_of`.
|
375
|
-
|
402
|
+
- **[validate_presence_of](lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb)**
|
376
403
|
tests usage of `validates_presence_of`.
|
377
|
-
|
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
|
-
|
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
|
-
|
411
|
+
- **[belong_to](lib/shoulda/matchers/active_record/association_matcher.rb)**
|
385
412
|
tests your `belongs_to` associations.
|
386
|
-
|
413
|
+
- **[define_enum_for](lib/shoulda/matchers/active_record/define_enum_for_matcher.rb)**
|
387
414
|
tests usage of the `enum` macro.
|
388
|
-
|
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
|
-
|
417
|
+
- **[have_delegated_type](lib/shoulda/matchers/active_record/association_matcher.rb#L687)**
|
391
418
|
tests usage of the `delegated_type` macro.
|
392
|
-
|
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
|
-
|
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
|
-
|
423
|
+
- **[have_implicit_order_column](lib/shoulda/matchers/active_record/have_implicit_order_column.rb)**
|
397
424
|
tests usage of `implicit_order_column`.
|
398
|
-
|
425
|
+
- **[have_many](lib/shoulda/matchers/active_record/association_matcher.rb#L328)**
|
399
426
|
tests your `has_many` associations.
|
400
|
-
|
427
|
+
- **[have_many_attached](lib/shoulda/matchers/active_record/have_attached_matcher.rb)**
|
401
428
|
tests your `has_many_attached` associations.
|
402
|
-
|
429
|
+
- **[have_one](lib/shoulda/matchers/active_record/association_matcher.rb#L598)**
|
403
430
|
tests your `has_one` associations.
|
404
|
-
|
431
|
+
- **[have_one_attached](lib/shoulda/matchers/active_record/have_attached_matcher.rb)**
|
405
432
|
tests your `has_one_attached` associations.
|
406
|
-
|
433
|
+
- **[have_readonly_attribute](lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb)**
|
407
434
|
tests usage of the `attr_readonly` macro.
|
408
|
-
|
435
|
+
- **[have_rich_text](lib/shoulda/matchers/active_record/have_rich_text_matcher.rb)**
|
409
436
|
tests your `has_rich_text` associations.
|
410
|
-
|
437
|
+
- **[serialize](lib/shoulda/matchers/active_record/serialize_matcher.rb)** tests
|
411
438
|
usage of the `serialize` macro.
|
412
|
-
|
439
|
+
- **[validate_uniqueness_of](lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb)**
|
413
440
|
tests usage of `validates_uniqueness_of`.
|
414
|
-
|
441
|
+
- **[normalize](lib/shoulda/matchers/active_record/normalize_matcher.rb)** tests
|
415
442
|
usage of the `normalize` macro
|
416
|
-
|
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
|
-
|
448
|
+
- **[filter_param](lib/shoulda/matchers/action_controller/filter_param_matcher.rb)**
|
422
449
|
tests parameter filtering configuration.
|
423
|
-
|
424
|
-
that an action
|
425
|
-
|
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
|
-
|
454
|
+
- **[render_template](lib/shoulda/matchers/action_controller/render_template_matcher.rb)**
|
428
455
|
tests that an action renders a template.
|
429
|
-
|
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
|
-
|
458
|
+
- **[rescue_from](lib/shoulda/matchers/action_controller/rescue_from_matcher.rb)**
|
432
459
|
tests usage of the `rescue_from` macro.
|
433
|
-
|
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
|
-
|
462
|
+
- **[route](lib/shoulda/matchers/action_controller/route_matcher.rb)** tests
|
436
463
|
your routes.
|
437
|
-
|
464
|
+
- **[set_session](lib/shoulda/matchers/action_controller/set_session_matcher.rb)**
|
438
465
|
makes assertions on the `session` hash.
|
439
|
-
|
466
|
+
- **[set_flash](lib/shoulda/matchers/action_controller/set_flash_matcher.rb)**
|
440
467
|
makes assertions on the `flash` hash.
|
441
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
477
|
+
- **[route](lib/shoulda/matchers/action_controller/route_matcher.rb)** tests
|
451
478
|
your routes.
|
452
479
|
|
453
480
|
### Independent matchers
|
454
481
|
|
455
|
-
|
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
|
-
|
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
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
|
@@ -19,10 +19,7 @@ module Shoulda
|
|
19
19
|
def matches?(subject)
|
20
20
|
self.subject = ModelReflector.new(subject, name)
|
21
21
|
|
22
|
-
if
|
23
|
-
:counter_cache,
|
24
|
-
counter_cache,
|
25
|
-
)
|
22
|
+
if correct_value?
|
26
23
|
true
|
27
24
|
else
|
28
25
|
self.missing_option = "#{name} should have #{description}"
|
@@ -34,9 +31,42 @@ module Shoulda
|
|
34
31
|
|
35
32
|
attr_accessor :subject, :counter_cache, :name
|
36
33
|
|
34
|
+
def correct_value?
|
35
|
+
expected = normalize_value
|
36
|
+
|
37
|
+
if expected.is_a?(Hash)
|
38
|
+
option_verifier.correct_for_hash?(
|
39
|
+
:counter_cache,
|
40
|
+
expected,
|
41
|
+
)
|
42
|
+
else
|
43
|
+
option_verifier.correct_for_string?(
|
44
|
+
:counter_cache,
|
45
|
+
expected,
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
37
50
|
def option_verifier
|
38
51
|
@_option_verifier ||= OptionVerifier.new(subject)
|
39
52
|
end
|
53
|
+
|
54
|
+
def normalize_value
|
55
|
+
if Rails::VERSION::STRING >= '7.2'
|
56
|
+
case counter_cache
|
57
|
+
when true
|
58
|
+
{ active: true, column: nil }
|
59
|
+
when String, Symbol
|
60
|
+
{ active: true, column: counter_cache.to_s }
|
61
|
+
when Hash
|
62
|
+
{ active: true, column: nil }.merge!(counter_cache)
|
63
|
+
else
|
64
|
+
raise ArgumentError, 'Invalid counter_cache option'
|
65
|
+
end
|
66
|
+
else
|
67
|
+
counter_cache
|
68
|
+
end
|
69
|
+
end
|
40
70
|
end
|
41
71
|
end
|
42
72
|
end
|
@@ -655,11 +655,22 @@ module Shoulda
|
|
655
655
|
end
|
656
656
|
|
657
657
|
def actual_default_value
|
658
|
-
attribute_schema = model.
|
658
|
+
attribute_schema = if model.respond_to?(:_default_attributes)
|
659
|
+
model._default_attributes[attribute_name.to_s]
|
660
|
+
else
|
661
|
+
model.attributes_to_define_after_schema_loads[attribute_name.to_s]
|
662
|
+
end
|
663
|
+
|
664
|
+
if Kernel.const_defined?('ActiveModel::Attribute::UserProvidedDefault') &&
|
665
|
+
attribute_schema.is_a?(::ActiveModel::Attribute::UserProvidedDefault)
|
666
|
+
attribute_schema = attribute_schema.marshal_dump
|
667
|
+
end
|
659
668
|
|
660
669
|
value = case attribute_schema
|
661
670
|
in [_, { default: default_value } ]
|
662
671
|
default_value
|
672
|
+
in [_, default_value, *]
|
673
|
+
default_value
|
663
674
|
in [_, default_value]
|
664
675
|
default_value
|
665
676
|
end
|
@@ -747,7 +758,7 @@ module Shoulda
|
|
747
758
|
def to_hash(value)
|
748
759
|
if value.is_a?(Array)
|
749
760
|
value.each_with_index.inject({}) do |hash, (item, index)|
|
750
|
-
hash.merge(item.to_s => index)
|
761
|
+
hash.merge!(item.to_s => index)
|
751
762
|
end
|
752
763
|
else
|
753
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)
|
@@ -168,8 +168,27 @@ module Shoulda
|
|
168
168
|
# should delegate_method(:plan).to(:subscription).allow_nil
|
169
169
|
# end
|
170
170
|
#
|
171
|
-
#
|
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
|
-
|
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
|
-
|
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
|
|
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
|
+
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:
|
17
|
+
date: 2025-04-25 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: activesupport
|