rspec-grape-entity 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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__)