rspec-grape-entity 0.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rspec.yml +26 -0
  3. data/.github/workflows/rubocop.yml +30 -0
  4. data/.gitignore +17 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +13 -0
  7. data/Gemfile +13 -0
  8. data/LICENSE +21 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +695 -0
  11. data/Rakefile +12 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/lib/rspec-grape-entity.rb +3 -0
  15. data/lib/rspec_grape_entity/describe_exposure.rb +125 -0
  16. data/lib/rspec_grape_entity/dsl.rb +16 -0
  17. data/lib/rspec_grape_entity/its_exposure.rb +72 -0
  18. data/lib/rspec_grape_entity/matchers/be_a_exposure_type_matcher.rb +33 -0
  19. data/lib/rspec_grape_entity/matchers/be_merged_matcher.rb +21 -0
  20. data/lib/rspec_grape_entity/matchers/be_safe_matcher.rb +21 -0
  21. data/lib/rspec_grape_entity/matchers/be_using_class_matcher.rb +21 -0
  22. data/lib/rspec_grape_entity/matchers/have_conditions_met_matcher.rb +36 -0
  23. data/lib/rspec_grape_entity/matchers/have_formatting_matcher.rb +39 -0
  24. data/lib/rspec_grape_entity/matchers/have_key_matcher.rb +21 -0
  25. data/lib/rspec_grape_entity/matchers/have_root_matcher.rb +33 -0
  26. data/lib/rspec_grape_entity/matchers/include_documentation_matcher.rb +26 -0
  27. data/lib/rspec_grape_entity/matchers/matcher_helpers.rb +24 -0
  28. data/lib/rspec_grape_entity/matchers/override_exposure_matcher.rb +21 -0
  29. data/lib/rspec_grape_entity/version.rb +5 -0
  30. data/lib/rspec_grape_entity.rb +28 -0
  31. data/rspec-grape-entity.gemspec +37 -0
  32. data/spec/describe_exposure_spec.rb +29 -0
  33. data/spec/entities/test_entity_spec.rb +150 -0
  34. data/spec/its_exposure_spec.rb +16 -0
  35. data/spec/matchers/be_a_exposure_type_matcher_spec.rb +106 -0
  36. data/spec/matchers/be_merged_matcher_spec.rb +19 -0
  37. data/spec/matchers/be_safe_matcher_spec.rb +19 -0
  38. data/spec/matchers/be_using_class_matcher_spec.rb +15 -0
  39. data/spec/matchers/have_conditions_met_matcher_spec.rb +40 -0
  40. data/spec/matchers/have_formatting_matcher_spec.rb +25 -0
  41. data/spec/matchers/have_key_matcher_spec.rb +23 -0
  42. data/spec/matchers/have_root_matcher_spec.rb +47 -0
  43. data/spec/matchers/include_documentation_matcher_spec.rb +31 -0
  44. data/spec/matchers/matcher_helpers_spec.rb +76 -0
  45. data/spec/matchers/override_exposure_matcher_spec.rb +19 -0
  46. data/spec/spec_helper.rb +21 -0
  47. data/spec/support/test_entity.rb +23 -0
  48. data/spec/support/user_entity.rb +12 -0
  49. metadata +181 -0
data/README.md ADDED
@@ -0,0 +1,695 @@
1
+ ![RSpec](https://github.com/jefawks3/rspec-grape-entity/actions/workflows/rspec.yml/badge.svg)
2
+ ![Rubocop](https://github.com/jefawks3/rspec-grape-entity/actions/workflows/rubocop.yml/badge.svg)
3
+
4
+
5
+ # Table of Contents
6
+
7
+ - [Rspec::Grape::Entity](#rspecgrapeentity)
8
+ - [Introduction](#introduction)
9
+ - [Installation](#installation)
10
+ - [Include Rspec::Grape::Entity](#include-rspecgrapeentity)
11
+ - [Usage](#usage)
12
+ - [Example](#example)
13
+ - [Entity Matchers](#entity-matchers)
14
+ - [`root`](#have_rootplural)
15
+ - [Exposure Matchers](#exposure-matchers)
16
+ - [`be_a_block_exposure`](#be_a_block_exposure)
17
+ - [`be_a_delegator_exposure`](#be_a_delegator_exposure)
18
+ - [`be_a_formatter_exposure`](#be_a_formatter_exposure)
19
+ - [`be_a_formatter_block_exposure`](#be_a_formatter_block_exposure)
20
+ - [`be_a_nesting_exposure`](#be_a_nesting_exposure)
21
+ - [`be_a_represent_exposure`](#be_a_represent_exposure)
22
+ - [`be_merged`](#be_merged)
23
+ - [`be_safe`](#be_safe)
24
+ - [`be_using_class`](#be_using_classentity)
25
+ - [`have_conditions_met`](#have_conditions_metobject)
26
+ - [`have_formatting`](#have_formattingformatter)
27
+ - [`have_key`](#have_keykey)
28
+ - [`include_documentation`](#include_documentationdocs)
29
+ - [`override_exposure`](#override_exposure)
30
+ - [Development](#development)
31
+ - [Report Issues](#report-issues)
32
+ - [License](#license)
33
+
34
+ # Rspec::Grape::Entity
35
+
36
+ ## Introduction
37
+
38
+ This gem, inspired by [rspec-its](https://github.com/rspec/rspec-its), provides the `its_exposure` and `describe_exposure`
39
+ short-hand to specify the expected configuration of a given [GrapeEntity](https://github.com/ruby-grape/grape-entity)
40
+ exposure.
41
+
42
+ ## Installation
43
+
44
+ Add this line to your application's Gemfile:
45
+
46
+ ```ruby
47
+ gem 'rspec-grape-entity', group: :test
48
+ ```
49
+
50
+ And then execute:
51
+
52
+ $ bundle install
53
+
54
+ Or install it yourself as:
55
+
56
+ $ gem install rspec-grape-entity
57
+
58
+ ## Include `RSpec::Grape::Entity`
59
+
60
+ Include the `RSpec::Grape::Entity` matchers automatically by defining the described `type` as `:grape_entity` or by
61
+ including the `RSpec::Grape::Entity::DSL` directly.
62
+
63
+ ```ruby
64
+ # Automatic
65
+ describe MyEntity, type: :grape_entity do
66
+ # ... tests
67
+ end
68
+
69
+ # Manually
70
+ describe MyEntity do
71
+ include RSpec::Grape::Entity::DSL
72
+
73
+ # ... tests
74
+ end
75
+ ```
76
+
77
+ ## Usage
78
+
79
+ Use the `describe_exposure` or `its_exposure` methods to generate a nested example group that specifies the expected
80
+ exposure of an attribute of the entity using `should`, `should_not` or `is_expected`.
81
+
82
+ `describe_exposure` and `its_exposure` accepts a symbol or string.
83
+
84
+ ```ruby
85
+ describe_exposure :id do
86
+ #...
87
+ end
88
+
89
+ describe_exposure 'id' do
90
+ #...
91
+ end
92
+
93
+ its_exposure(:id) { is_expected.not_to be_nil }
94
+ its_exposure('id') { is_expected.not_to be_nil }
95
+ ```
96
+
97
+ You can use a string with dots to specify a nested exposure attribute.
98
+
99
+ ```ruby
100
+ describe_exposure 'status.value' do
101
+ #...
102
+ end
103
+
104
+ describe_exposure 'status.changed_at' do
105
+ #...
106
+ end
107
+
108
+ its_exposure('status.value') { is_expected.not_to be_nil }
109
+ its_exposure('status.changed_at') { is_expected.not_to be_nil }
110
+ ```
111
+
112
+ ## Example
113
+
114
+ ```ruby
115
+ class TestEntity < Grape::Entity
116
+ root "test_items", "test_item"
117
+
118
+ format_with :iso_timestamp, &:iso8601
119
+
120
+ expose :id, documentation: { type: Integer, desc: "The record id" }
121
+ expose :record_status, as: :status, if: :all
122
+ expose :user, safe: true, using: UserEntity, if: { type: :admin }
123
+ expose :custom_data, merge: true do |_, _|
124
+ {
125
+ foo: :bar
126
+ }
127
+ end
128
+ expose :permissions, override: true do
129
+ expose :read
130
+ expose :update
131
+ expose :destroy
132
+ end
133
+ expose :created_at, format_with: ->(date) { date.iso8601 }, if: ->(instance, _) { instance.has_date }
134
+ expose :updated_at, format_with: :iso_timestamp
135
+ end
136
+
137
+ RSpec.describe TestEntity, type: :grape_entity do
138
+ let(:object) do
139
+ OpenStruct.new id: 1,
140
+ record_status: "active",
141
+ user: OpenStruct.new,
142
+ read: true,
143
+ update: false,
144
+ destroy: false,
145
+ created_at: Time.utc(2022, 1, 1, 15, 0, 0),
146
+ updated_at: Time.now,
147
+ has_date: true
148
+ end
149
+
150
+ it { expect(described_class).to have_root("test_items").with_singular("test_item") }
151
+
152
+ context "when using its_exposure" do
153
+ let(:object_without_date) { OpenStruct.new has_date: false }
154
+
155
+ its_exposure(:id) { is_expected.to be_a_delegator_exposure }
156
+ its_exposure(:id) { is_expected.to include_documentation type: Integer, desc: "The record id" }
157
+ its_exposure(:id) { is_expected.not_to be_safe }
158
+ its_exposure(:id) { is_expected.not_to be_merged }
159
+ its_exposure(:id) { is_expected.not_to override_exposure }
160
+ its_exposure(:record_status) { is_expected.to be_a_delegator_exposure }
161
+ its_exposure(:record_status) { is_expected.to have_key :status }
162
+ its_exposure(:record_status) { is_expected.to have_conditions_met(object).with_options(all: :something) }
163
+ its_exposure(:record_status) { is_expected.to_not have_conditions_met object }
164
+ its_exposure(:record_status) { is_expected.not_to be_safe }
165
+ its_exposure(:record_status) { is_expected.not_to be_merged }
166
+ its_exposure(:record_status) { is_expected.not_to override_exposure }
167
+ its_exposure(:user) { is_expected.to be_a_represent_exposure }
168
+ its_exposure(:user) { is_expected.to be_using_class UserEntity }
169
+ its_exposure(:user) { is_expected.to have_conditions_met(object).with_options(type: :admin) }
170
+ its_exposure(:user) { is_expected.not_to have_conditions_met(object).with_options(type: :user) }
171
+ its_exposure(:user) { is_expected.not_to have_conditions_met object }
172
+ its_exposure(:custom_data) { is_expected.to be_a_block_exposure }
173
+ its_exposure(:custom_data) { is_expected.not_to be_safe }
174
+ its_exposure(:custom_data) { is_expected.to be_merged }
175
+ its_exposure(:custom_data) { is_expected.not_to override_exposure }
176
+ its_exposure(:permissions) { is_expected.to be_a_nesting_exposure }
177
+ its_exposure(:permissions) { is_expected.not_to be_safe }
178
+ its_exposure(:permissions) { is_expected.not_to be_merged }
179
+ its_exposure(:permissions) { is_expected.to override_exposure }
180
+ its_exposure("permissions.read") { is_expected.to be_a_delegator_exposure }
181
+ its_exposure("permissions.read") { is_expected.not_to be_safe }
182
+ its_exposure("permissions.read") { is_expected.not_to be_merged }
183
+ its_exposure("permissions.read") { is_expected.not_to override_exposure }
184
+ its_exposure("permissions.update") { is_expected.to be_a_delegator_exposure }
185
+ its_exposure("permissions.update") { is_expected.not_to be_safe }
186
+ its_exposure("permissions.update") { is_expected.not_to be_merged }
187
+ its_exposure("permissions.update") { is_expected.not_to override_exposure }
188
+ its_exposure("permissions.destroy") { is_expected.to be_a_delegator_exposure }
189
+ its_exposure("permissions.destroy") { is_expected.not_to be_safe }
190
+ its_exposure("permissions.destroy") { is_expected.not_to be_merged }
191
+ its_exposure("permissions.destroy") { is_expected.not_to override_exposure }
192
+ its_exposure("created_at") { is_expected.to be_a_formatter_block_exposure }
193
+ its_exposure("created_at") { is_expected.to have_formatting("2022-01-01T15:00:00Z").with_object(object) }
194
+ its_exposure("created_at") { is_expected.not_to be_safe }
195
+ its_exposure("created_at") { is_expected.not_to be_merged }
196
+ its_exposure("created_at") { is_expected.not_to override_exposure }
197
+ its_exposure("created_at") { is_expected.to have_conditions_met object }
198
+ its_exposure("created_at") { is_expected.to_not have_conditions_met object_without_date }
199
+ end
200
+
201
+ context "when using describe_exposure" do
202
+ shared_examples "has permissions" do |permission|
203
+ describe_exposure "permissions.#{permission}" do
204
+ it { is_expected.to be_a_delegator_exposure }
205
+ it { is_expected.not_to be_safe }
206
+ it { is_expected.not_to be_merged }
207
+ it { is_expected.not_to override_exposure }
208
+ end
209
+ end
210
+
211
+ describe_exposure :id do
212
+ it { is_expected.to be_a_delegator_exposure }
213
+ it { is_expected.to include_documentation type: Integer, desc: "The record id" }
214
+ it { is_expected.not_to be_safe }
215
+ it { is_expected.not_to be_merged }
216
+ it { is_expected.not_to override_exposure }
217
+ end
218
+
219
+ describe_exposure :record_status do
220
+ it { is_expected.to be_a_delegator_exposure }
221
+ it { is_expected.to have_key :status }
222
+ it { is_expected.to have_conditions_met(object).with_options(all: :something) }
223
+ it { is_expected.to_not have_conditions_met object }
224
+ it { is_expected.not_to be_safe }
225
+ it { is_expected.not_to be_merged }
226
+ it { is_expected.not_to override_exposure }
227
+ end
228
+
229
+ describe_exposure :user do
230
+ it { is_expected.to be_a_represent_exposure }
231
+ it { is_expected.to be_using_class UserEntity }
232
+
233
+ context "when type is an admin" do
234
+ it { is_expected.to have_conditions_met(object).with_options(type: :admin) }
235
+ end
236
+
237
+ context "when type is not an admin" do
238
+ it { is_expected.not_to have_conditions_met(object).with_options(type: :user) }
239
+ end
240
+
241
+ context "when no type is declared" do
242
+ it { is_expected.not_to have_conditions_met object }
243
+ end
244
+ end
245
+
246
+ describe_exposure :custom_data do
247
+ it { is_expected.to be_a_block_exposure }
248
+ it { is_expected.not_to be_safe }
249
+ it { is_expected.to be_merged }
250
+ it { is_expected.not_to override_exposure }
251
+ end
252
+
253
+ describe_exposure :permissions do
254
+ it { is_expected.to be_a_nesting_exposure }
255
+ it { is_expected.not_to be_safe }
256
+ it { is_expected.not_to be_merged }
257
+ it { is_expected.to override_exposure }
258
+ end
259
+
260
+ it_behaves_like "has permissions", "read"
261
+ it_behaves_like "has permissions", "update"
262
+ it_behaves_like "has permissions", "destroy"
263
+
264
+ describe_exposure :created_at do
265
+ it { is_expected.to be_a_formatter_block_exposure }
266
+ it { is_expected.to have_formatting("2022-01-01T15:00:00Z").with_object(object) }
267
+ it { is_expected.not_to be_safe }
268
+ it { is_expected.not_to be_merged }
269
+ it { is_expected.not_to override_exposure }
270
+
271
+ context "when has date" do
272
+ it { is_expected.to have_conditions_met object }
273
+ end
274
+
275
+ context "when does not have date" do
276
+ let(:object) { OpenStruct.new has_date: false }
277
+
278
+ it { is_expected.not_to have_conditions_met object }
279
+ end
280
+ end
281
+ end
282
+ end
283
+ ```
284
+
285
+ ## Entity Matchers
286
+
287
+ ### `have_root(plural)`
288
+
289
+ Test that only the plural definition is set.
290
+
291
+ ```ruby
292
+ class Entity < Grape::Entity
293
+ root "items"
294
+ end
295
+
296
+ RSpec.describe Entity, type: :grape_entity do
297
+ it { expect(described_class).to have_root "items" }
298
+ end
299
+ ```
300
+
301
+ Chain `singular(singular)` to specify the expected singular definition.
302
+
303
+ ```ruby
304
+ class Entity < Grape::Entity
305
+ root "items", "item"
306
+ end
307
+
308
+ RSpec.describe Entity, type: :grape_entity do
309
+ it { expect(described_class).to have_root("items").singular("item") }
310
+ end
311
+ ```
312
+
313
+ ## Exposure Matchers
314
+
315
+ Use `should`, `should_not`, `will`, `will_not`, `is_expected` to specify the expected value of the exposed attribute.
316
+
317
+ ### `be_a_block_exposure`
318
+
319
+ Test that the exposed attribute is a block exposure.
320
+
321
+ ```ruby
322
+ class Entity < Grape::Entity
323
+ expose :block do |item, options|
324
+ #...something
325
+ end
326
+ end
327
+
328
+ RSpec.describe Entity, type: :grape_entity do
329
+ describe_exposure :block do
330
+ it { is_expected.to be_a_block_exposure }
331
+ end
332
+
333
+ # Or
334
+
335
+ its_exposure(:block) { is_expected.to be_a_block_exposure }
336
+ end
337
+ ```
338
+
339
+ ### `be_a_delegator_exposure`
340
+
341
+ Test that the exposed attribute is a delegator exposure.
342
+
343
+ ```ruby
344
+ class Entity < Grape::Entity
345
+ expose :id
346
+ end
347
+
348
+ RSpec.describe Entity, type: :grape_entity do
349
+ describe_exposure :id do
350
+ it { is_expected.to be_a_delegator_exposure }
351
+ end
352
+
353
+ # Or
354
+
355
+ its_exposure(:id) { is_expected.to be_a_delegator_exposure }
356
+ end
357
+ ```
358
+
359
+ ### `be_a_formatter_exposure`
360
+
361
+ Test that the exposed attribute is a formatter exposure using a symbol.
362
+
363
+ ```ruby
364
+ class Entity < Grape::Entity
365
+ expose :created_at, format_with: :iso_timestamp
366
+ end
367
+
368
+ RSpec.describe Entity, type: :grape_entity do
369
+ describe_exposure :created_at do
370
+ it { is_expected.to be_a_formatter_exposure }
371
+ end
372
+
373
+ # Or
374
+
375
+ its_exposure(:created_at) { is_expected.to be_a_formatter_exposure }
376
+ end
377
+ ```
378
+
379
+ ### `be_a_formatter_block_exposure`
380
+
381
+ Test that the exposed attribute is a formatter exposure using a proc.
382
+
383
+ ```ruby
384
+ class Entity < Grape::Entity
385
+ expose :created_at, format_with: ->(date) { date.iso8601 }
386
+ end
387
+
388
+ RSpec.describe Entity, type: :grape_entity do
389
+ describe_exposure :created_at do
390
+ it { is_expected.to be_a_formatter_block_exposure }
391
+ end
392
+
393
+ # Or
394
+
395
+ its_exposure(:created_at) { is_expected.to be_a_formatter_block_exposure }
396
+ end
397
+ ```
398
+
399
+ ### `be_a_nesting_exposure`
400
+
401
+ Test that the exposed attribute is a nesting exposure.
402
+
403
+ ```ruby
404
+ class Entity < Grape::Entity
405
+ expose :status do
406
+ expose :status, as: :value
407
+ expose :changed_at, as: :status_changed_at
408
+ end
409
+ end
410
+
411
+ RSpec.describe Entity, type: :grape_entity do
412
+ describe_exposure :status do
413
+ it { is_expected.to be_a_nesting_exposure }
414
+ end
415
+
416
+ # Or
417
+
418
+ its_exposure(:status) { is_expected.to be_a_nesting_exposure }
419
+ end
420
+ ```
421
+
422
+ ### `be_a_represent_exposure`
423
+
424
+ Test that the exposed attribute is a represented exposure.
425
+
426
+ ```ruby
427
+ class Entity < Grape::Entity
428
+ expose :user, using: UserEntity
429
+ end
430
+
431
+ RSpec.describe Entity, type: :grape_entity do
432
+ describe_exposure :user do
433
+ it { is_expected.to be_a_represent_exposure }
434
+ end
435
+
436
+ # Or
437
+
438
+ its_exposure(:user) { is_expected.to be_a_represent_exposure }
439
+ end
440
+ ```
441
+
442
+ ### `be_merged`
443
+
444
+ Test that the exposed attribute is merged into the parent.
445
+
446
+ ```ruby
447
+ class Entity < Grape::Entity
448
+ expose :user, using: UserEntity, merge: true
449
+ end
450
+
451
+ RSpec.describe Entity, type: :grape_entity do
452
+ describe_exposure :user do
453
+ it { is_expected.to be_merged }
454
+ end
455
+
456
+ # Or
457
+
458
+ its_exposure(:user) { is_expected.to be_merged }
459
+ end
460
+ ```
461
+
462
+ ### `be_safe`
463
+
464
+ Test that the exposed attribute is safe.
465
+
466
+ ```ruby
467
+ class Entity < Grape::Entity
468
+ expose :user, using: UserEntity, safe: true
469
+ end
470
+
471
+ RSpec.describe Entity, type: :grape_entity do
472
+ describe_exposure :user do
473
+ it { is_expected.to be_safe }
474
+ end
475
+
476
+ # Or
477
+
478
+ its_exposure(:user) { is_expected.to be_safe }
479
+ end
480
+ ```
481
+
482
+ ### `be_using_class(entity)`
483
+
484
+ Test that the exposed attribute uses an entity presenter.
485
+
486
+ ```ruby
487
+ class Entity < Grape::Entity
488
+ expose :user, using: UserEntity
489
+ end
490
+
491
+ RSpec.describe Entity, type: :grape_entity do
492
+ describe_exposure :user do
493
+ it { is_expected.to be_using_class(UserEntity) }
494
+ end
495
+
496
+ # Or
497
+
498
+ its_exposure(:user) { is_expected.to be_using_class(UserEntity) }
499
+ end
500
+ ```
501
+
502
+ ### `have_conditions_met(object)`
503
+
504
+ Test that the exposed attribute conditions are met with a given object.
505
+
506
+ ```ruby
507
+ class Entity < Grape::Entity
508
+ expose :protected, if: ->(instance) { instance.is_a?(Admin) }
509
+ end
510
+
511
+ RSpec.describe Entity, type: :grape_entity do
512
+ let(:admin) { Admin.new }
513
+ let(:user) { User.new }
514
+
515
+ describe_exposure :protected do
516
+ it { is_expected.to have_conditions_met(admin) }
517
+ it { is_expected.not_to have_conditions_met(user) }
518
+ end
519
+
520
+ # Or
521
+
522
+ its_exposure(:protected) { is_expected.to have_conditions_met(admin) }
523
+ its_exposure(:protected) { is_expected.not_to have_conditions_met(user) }
524
+ end
525
+ ```
526
+
527
+ Chain with `with_options(options)` to pass an options hash to the attribute exposure's conditions.
528
+
529
+ ```ruby
530
+ class Entity < Grape::Entity
531
+ expose :status, if: :all
532
+ expose :secret, if: { type: :admin }
533
+ end
534
+
535
+ RSpec.describe Entity, type: :grape_entity do
536
+ let(:admin) { Admin.new }
537
+
538
+ describe_exposure :status do
539
+ it { is_expected.to have_conditions_met(admin).with_options(all: true) }
540
+ it { is_expected.not_to have_conditions_met(admin) }
541
+ end
542
+
543
+ describe_exposure :secret do
544
+ it { is_expected.to have_conditions_met(admin).with_options(type: :admin) }
545
+ it { is_expected.not_to have_conditions_met(admin).with_options(type: :user) }
546
+ it { is_expected.not_to have_conditions_met(admin) }
547
+ end
548
+
549
+ # Or
550
+
551
+ its_exposure(:status) { is_expected.to have_conditions_met(admin).with_options(all: true) }
552
+ its_exposure(:status) { is_expected.not_to have_conditions_met(admin) }
553
+ its_exposure(:secret) { is_expected.to have_conditions_met(admin).with_options(type: :admin) }
554
+ its_exposure(:secret) { is_expected.not_to have_conditions_met(admin).with_options(type: :user) }
555
+ its_exposure(:secret) { is_expected.not_to have_conditions_met(admin) }
556
+ end
557
+ ```
558
+
559
+ ### `have_formatting(formatter)`
560
+
561
+ Test that the exposed attribute uses a given formatter.
562
+
563
+ ```ruby
564
+ class Entity < Grape::Entity
565
+ expose :created_at, format_with: :iso_timestamp
566
+ end
567
+
568
+ RSpec.describe Entity, type: :grape_entity do
569
+ describe_exposure :created_at do
570
+ it { is_expected.to have_formatting :iso_timestamp }
571
+ end
572
+
573
+ # Or
574
+
575
+ its_exposure(:created_at) { is_expected.to have_formatting :iso_timestamp }
576
+ end
577
+ ```
578
+
579
+ Chain with `with_object(object)` when the formatter option is a Proc.
580
+
581
+ ```ruby
582
+ class Entity < Grape::Entity
583
+ expose :created_at, format_with: ->(date) { |date| date.iso8601 }
584
+ end
585
+
586
+ RSpec.describe Entity, type: :grape_entity do
587
+ let(:date) { Time.utc 2022, 1, 22, 17, 0, 0 }
588
+
589
+ describe_exposure :created_at do
590
+ it { is_expected.to have_formatting("2022-01-22T17:00:00Z").with_object(date) }
591
+ end
592
+
593
+ # Or
594
+
595
+ its_exposure(:created_at) { is_expected.to have_formatting("2022-01-22T17:00:00Z").with_object(date) }
596
+ end
597
+ ```
598
+
599
+ ### `have_key(key)`
600
+
601
+ Test that the exposed attribute uses an alias.
602
+
603
+ ```ruby
604
+ class Entity < Grape::Entity
605
+ expose :to_param, as: :id
606
+ end
607
+
608
+ RSpec.describe Entity, type: :grape_entity do
609
+ describe_exposure :to_param do
610
+ it { is_expected.to have_key :id }
611
+ end
612
+
613
+ # Or
614
+
615
+ its_exposure(:to_param) { is_expected.to have_key :id }
616
+ end
617
+ ```
618
+
619
+ ### `include_documentation(*docs)`
620
+
621
+ Test that the exposed attribute has the included documentation.
622
+
623
+ ```ruby
624
+ class Entity < Grape::Entity
625
+ expose :id, documentation: { type: Integer, desc: "Entity id" }
626
+ end
627
+
628
+ RSpec.describe Entity, type: :grape_entity do
629
+ describe_exposure :id do
630
+ it { is_expected.to have_documentation :type, :desc }
631
+ it { is_expected.to have_documentation :type }
632
+ it { is_expected.to have_documentation :desc }
633
+ it { is_expected.to have_documentation type: Integer }
634
+ it { is_expected.to have_documentation desc: "Entity id" }
635
+ it { is_expected.to have_documentation type: Integer, desc: "Entity id" }
636
+ end
637
+
638
+ # Or
639
+
640
+ its_exposure(:id) { is_expected.to have_documentation :type, :desc }
641
+ its_exposure(:id) { is_expected.to have_documentation :type }
642
+ its_exposure(:id) { is_expected.to have_documentation :desc }
643
+ its_exposure(:id) { is_expected.to have_documentation type: Integer }
644
+ its_exposure(:id) { is_expected.to have_documentation desc: "Entity id" }
645
+ its_exposure(:id) { is_expected.to have_documentation type: Integer, desc: "Entity id" }
646
+ end
647
+ ```
648
+
649
+ ### `override_exposure`
650
+
651
+ Test that the exposed attribute uses an alias.
652
+
653
+ ```ruby
654
+ class BaseEntity < Grape::Entity
655
+ expose :id
656
+ end
657
+
658
+ class Entity < BaseEntity
659
+ expose :id, override: true do |instance, options|
660
+ #...
661
+ end
662
+ end
663
+
664
+ RSpec.describe Entity, type: :grape_entity do
665
+ describe_exposure :id do
666
+ it { is_expected.to override_exposure }
667
+ end
668
+
669
+ # Or
670
+
671
+ its_exposure(:to_param) { is_expected.to override_exposure }
672
+ end
673
+ ```
674
+
675
+ ## Development
676
+
677
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
678
+
679
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
680
+
681
+ ## Report Issues
682
+
683
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jefawks3/rspec-grape-entity.
684
+
685
+ ## Contributing
686
+
687
+ 1. Fork it
688
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
689
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
690
+ 4. Push to the branch (`git push origin my-new-feature`)
691
+ 5. Create new Pull Request
692
+
693
+ ## License
694
+
695
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "rspec-grape-entity"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)