Table of Contents
- Rspec::Grape::Entity
- Introduction
- Installation
- Include Rspec::Grape::Entity
- Usage
- Example
- Entity Matchers
root
- Exposure Matchers
be_a_block_exposurebe_a_delegator_exposurebe_a_formatter_exposurebe_a_formatter_block_exposurebe_a_nesting_exposurebe_a_represent_exposurebe_mergedbe_safebe_using_classhave_conditions_methave_formattinghave_keyinclude_documentationoverride_exposure
- Development
- Report Issues
- License
Rspec::Grape::Entity
Introduction
This gem, inspired by rspec-its, provides the its_exposure and describe_exposure
short-hand to specify the expected configuration of a given GrapeEntity
exposure.
Installation
Add this line to your application's Gemfile:
gem 'rspec-grape-entity', group: :testAnd then execute:
$ bundle install
Or install it yourself as:
$ gem install rspec-grape-entity
Include RSpec::Grape::Entity
Include the RSpec::Grape::Entity matchers automatically by defining the described type as :grape_entity or by
including the RSpec::Grape::Entity::DSL directly.
# Automatic
describe MyEntity, type: :grape_entity do
# ... tests
end
# Manually
describe MyEntity do
include RSpec::Grape::Entity::DSL
# ... tests
endUsage
Use the describe_exposure or its_exposure methods to generate a nested example group that specifies the expected
exposure of an attribute of the entity using should, should_not or is_expected.
describe_exposure and its_exposure accepts a symbol or string.
describe_exposure :id do
#...
end
describe_exposure 'id' do
#...
end
its_exposure(:id) { is_expected.not_to be_nil }
its_exposure('id') { is_expected.not_to be_nil }You can use a string with dots to specify a nested exposure attribute.
describe_exposure 'status.value' do
#...
end
describe_exposure 'status.changed_at' do
#...
end
its_exposure('status.value') { is_expected.not_to be_nil }
its_exposure('status.changed_at') { is_expected.not_to be_nil }Example
class TestEntity < Grape::Entity
root "test_items", "test_item"
format_with :iso_timestamp, &:iso8601
expose :id, documentation: { type: Integer, desc: "The record id" }
expose :record_status, as: :status, if: :all
expose :user, safe: true, using: UserEntity, if: { type: :admin }
expose :custom_data, merge: true do |_, _|
{
foo: :bar
}
end
expose :permissions, override: true do
expose :read
expose :update
expose :destroy
end
expose :created_at, format_with: ->(date) { date.iso8601 }, if: ->(instance, _) { instance.has_date }
expose :updated_at, format_with: :iso_timestamp
end
RSpec.describe TestEntity, type: :grape_entity do
let(:object) do
OpenStruct.new id: 1,
record_status: "active",
user: OpenStruct.new,
read: true,
update: false,
destroy: false,
created_at: Time.utc(2022, 1, 1, 15, 0, 0),
updated_at: Time.now,
has_date: true
end
it { expect(described_class).to have_root("test_items").with_singular("test_item") }
context "when using its_exposure" do
let(:object_without_date) { OpenStruct.new has_date: false }
its_exposure(:id) { is_expected.to be_a_delegator_exposure }
its_exposure(:id) { is_expected.to include_documentation type: Integer, desc: "The record id" }
its_exposure(:id) { is_expected.not_to be_safe }
its_exposure(:id) { is_expected.not_to be_merged }
its_exposure(:id) { is_expected.not_to override_exposure }
its_exposure(:record_status) { is_expected.to be_a_delegator_exposure }
its_exposure(:record_status) { is_expected.to have_key :status }
its_exposure(:record_status) { is_expected.to have_conditions_met(object).with_options(all: :something) }
its_exposure(:record_status) { is_expected.to_not have_conditions_met object }
its_exposure(:record_status) { is_expected.not_to be_safe }
its_exposure(:record_status) { is_expected.not_to be_merged }
its_exposure(:record_status) { is_expected.not_to override_exposure }
its_exposure(:user) { is_expected.to be_a_represent_exposure }
its_exposure(:user) { is_expected.to be_using_class UserEntity }
its_exposure(:user) { is_expected.to have_conditions_met(object).with_options(type: :admin) }
its_exposure(:user) { is_expected.not_to have_conditions_met(object).with_options(type: :user) }
its_exposure(:user) { is_expected.not_to have_conditions_met object }
its_exposure(:custom_data) { is_expected.to be_a_block_exposure }
its_exposure(:custom_data) { is_expected.not_to be_safe }
its_exposure(:custom_data) { is_expected.to be_merged }
its_exposure(:custom_data) { is_expected.not_to override_exposure }
its_exposure(:permissions) { is_expected.to be_a_nesting_exposure }
its_exposure(:permissions) { is_expected.not_to be_safe }
its_exposure(:permissions) { is_expected.not_to be_merged }
its_exposure(:permissions) { is_expected.to override_exposure }
its_exposure("permissions.read") { is_expected.to be_a_delegator_exposure }
its_exposure("permissions.read") { is_expected.not_to be_safe }
its_exposure("permissions.read") { is_expected.not_to be_merged }
its_exposure("permissions.read") { is_expected.not_to override_exposure }
its_exposure("permissions.update") { is_expected.to be_a_delegator_exposure }
its_exposure("permissions.update") { is_expected.not_to be_safe }
its_exposure("permissions.update") { is_expected.not_to be_merged }
its_exposure("permissions.update") { is_expected.not_to override_exposure }
its_exposure("permissions.destroy") { is_expected.to be_a_delegator_exposure }
its_exposure("permissions.destroy") { is_expected.not_to be_safe }
its_exposure("permissions.destroy") { is_expected.not_to be_merged }
its_exposure("permissions.destroy") { is_expected.not_to override_exposure }
its_exposure("created_at") { is_expected.to be_a_formatter_block_exposure }
its_exposure("created_at") { is_expected.to have_formatting("2022-01-01T15:00:00Z").with_object(object) }
its_exposure("created_at") { is_expected.not_to be_safe }
its_exposure("created_at") { is_expected.not_to be_merged }
its_exposure("created_at") { is_expected.not_to override_exposure }
its_exposure("created_at") { is_expected.to have_conditions_met object }
its_exposure("created_at") { is_expected.to_not have_conditions_met object_without_date }
end
context "when using describe_exposure" do
shared_examples "has permissions" do |permission|
describe_exposure "permissions.#{permission}" do
it { is_expected.to be_a_delegator_exposure }
it { is_expected.not_to be_safe }
it { is_expected.not_to be_merged }
it { is_expected.not_to override_exposure }
end
end
describe_exposure :id do
it { is_expected.to be_a_delegator_exposure }
it { is_expected.to include_documentation type: Integer, desc: "The record id" }
it { is_expected.not_to be_safe }
it { is_expected.not_to be_merged }
it { is_expected.not_to override_exposure }
end
describe_exposure :record_status do
it { is_expected.to be_a_delegator_exposure }
it { is_expected.to have_key :status }
it { is_expected.to have_conditions_met(object).with_options(all: :something) }
it { is_expected.to_not have_conditions_met object }
it { is_expected.not_to be_safe }
it { is_expected.not_to be_merged }
it { is_expected.not_to override_exposure }
end
describe_exposure :user do
it { is_expected.to be_a_represent_exposure }
it { is_expected.to be_using_class UserEntity }
context "when type is an admin" do
it { is_expected.to have_conditions_met(object).with_options(type: :admin) }
end
context "when type is not an admin" do
it { is_expected.not_to have_conditions_met(object).with_options(type: :user) }
end
context "when no type is declared" do
it { is_expected.not_to have_conditions_met object }
end
end
describe_exposure :custom_data do
it { is_expected.to be_a_block_exposure }
it { is_expected.not_to be_safe }
it { is_expected.to be_merged }
it { is_expected.not_to override_exposure }
end
describe_exposure :permissions do
it { is_expected.to be_a_nesting_exposure }
it { is_expected.not_to be_safe }
it { is_expected.not_to be_merged }
it { is_expected.to override_exposure }
end
it_behaves_like "has permissions", "read"
it_behaves_like "has permissions", "update"
it_behaves_like "has permissions", "destroy"
describe_exposure :created_at do
it { is_expected.to be_a_formatter_block_exposure }
it { is_expected.to have_formatting("2022-01-01T15:00:00Z").with_object(object) }
it { is_expected.not_to be_safe }
it { is_expected.not_to be_merged }
it { is_expected.not_to override_exposure }
context "when has date" do
it { is_expected.to have_conditions_met object }
end
context "when does not have date" do
let(:object) { OpenStruct.new has_date: false }
it { is_expected.not_to have_conditions_met object }
end
end
end
endEntity Matchers
have_root(plural)
Test that only the plural definition is set.
class Entity < Grape::Entity
root "items"
end
RSpec.describe Entity, type: :grape_entity do
it { expect(described_class).to have_root "items" }
endChain singular(singular) to specify the expected singular definition.
class Entity < Grape::Entity
root "items", "item"
end
RSpec.describe Entity, type: :grape_entity do
it { expect(described_class).to have_root("items").singular("item") }
endExposure Matchers
Use should, should_not, will, will_not, is_expected to specify the expected value of the exposed attribute.
be_a_block_exposure
Test that the exposed attribute is a block exposure.
class Entity < Grape::Entity
expose :block do |item, options|
#...something
end
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :block do
it { is_expected.to be_a_block_exposure }
end
# Or
its_exposure(:block) { is_expected.to be_a_block_exposure }
endbe_a_delegator_exposure
Test that the exposed attribute is a delegator exposure.
class Entity < Grape::Entity
expose :id
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :id do
it { is_expected.to be_a_delegator_exposure }
end
# Or
its_exposure(:id) { is_expected.to be_a_delegator_exposure }
endbe_a_formatter_exposure
Test that the exposed attribute is a formatter exposure using a symbol.
class Entity < Grape::Entity
expose :created_at, format_with: :iso_timestamp
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :created_at do
it { is_expected.to be_a_formatter_exposure }
end
# Or
its_exposure(:created_at) { is_expected.to be_a_formatter_exposure }
endbe_a_formatter_block_exposure
Test that the exposed attribute is a formatter exposure using a proc.
class Entity < Grape::Entity
expose :created_at, format_with: ->(date) { date.iso8601 }
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :created_at do
it { is_expected.to be_a_formatter_block_exposure }
end
# Or
its_exposure(:created_at) { is_expected.to be_a_formatter_block_exposure }
endbe_a_nesting_exposure
Test that the exposed attribute is a nesting exposure.
class Entity < Grape::Entity
expose :status do
expose :status, as: :value
expose :changed_at, as: :status_changed_at
end
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :status do
it { is_expected.to be_a_nesting_exposure }
end
# Or
its_exposure(:status) { is_expected.to be_a_nesting_exposure }
endbe_a_represent_exposure
Test that the exposed attribute is a represented exposure.
class Entity < Grape::Entity
expose :user, using: UserEntity
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :user do
it { is_expected.to be_a_represent_exposure }
end
# Or
its_exposure(:user) { is_expected.to be_a_represent_exposure }
endbe_merged
Test that the exposed attribute is merged into the parent.
class Entity < Grape::Entity
expose :user, using: UserEntity, merge: true
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :user do
it { is_expected.to be_merged }
end
# Or
its_exposure(:user) { is_expected.to be_merged }
endbe_safe
Test that the exposed attribute is safe.
class Entity < Grape::Entity
expose :user, using: UserEntity, safe: true
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :user do
it { is_expected.to be_safe }
end
# Or
its_exposure(:user) { is_expected.to be_safe }
endbe_using_class(entity)
Test that the exposed attribute uses an entity presenter.
class Entity < Grape::Entity
expose :user, using: UserEntity
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :user do
it { is_expected.to be_using_class(UserEntity) }
end
# Or
its_exposure(:user) { is_expected.to be_using_class(UserEntity) }
endhave_conditions_met(object)
Test that the exposed attribute conditions are met with a given object.
class Entity < Grape::Entity
expose :protected, if: ->(instance) { instance.is_a?(Admin) }
end
RSpec.describe Entity, type: :grape_entity do
let(:admin) { Admin.new }
let(:user) { User.new }
describe_exposure :protected do
it { is_expected.to have_conditions_met(admin) }
it { is_expected.not_to have_conditions_met(user) }
end
# Or
its_exposure(:protected) { is_expected.to have_conditions_met(admin) }
its_exposure(:protected) { is_expected.not_to have_conditions_met(user) }
endChain with with_options(options) to pass an options hash to the attribute exposure's conditions.
class Entity < Grape::Entity
expose :status, if: :all
expose :secret, if: { type: :admin }
end
RSpec.describe Entity, type: :grape_entity do
let(:admin) { Admin.new }
describe_exposure :status do
it { is_expected.to have_conditions_met(admin).with_options(all: true) }
it { is_expected.not_to have_conditions_met(admin) }
end
describe_exposure :secret do
it { is_expected.to have_conditions_met(admin).with_options(type: :admin) }
it { is_expected.not_to have_conditions_met(admin).with_options(type: :user) }
it { is_expected.not_to have_conditions_met(admin) }
end
# Or
its_exposure(:status) { is_expected.to have_conditions_met(admin).with_options(all: true) }
its_exposure(:status) { is_expected.not_to have_conditions_met(admin) }
its_exposure(:secret) { is_expected.to have_conditions_met(admin).with_options(type: :admin) }
its_exposure(:secret) { is_expected.not_to have_conditions_met(admin).with_options(type: :user) }
its_exposure(:secret) { is_expected.not_to have_conditions_met(admin) }
endhave_formatting(formatter)
Test that the exposed attribute uses a given formatter.
class Entity < Grape::Entity
expose :created_at, format_with: :iso_timestamp
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :created_at do
it { is_expected.to have_formatting :iso_timestamp }
end
# Or
its_exposure(:created_at) { is_expected.to have_formatting :iso_timestamp }
endChain with with_object(object) when the formatter option is a Proc.
class Entity < Grape::Entity
expose :created_at, format_with: ->(date) { |date| date.iso8601 }
end
RSpec.describe Entity, type: :grape_entity do
let(:date) { Time.utc 2022, 1, 22, 17, 0, 0 }
describe_exposure :created_at do
it { is_expected.to have_formatting("2022-01-22T17:00:00Z").with_object(date) }
end
# Or
its_exposure(:created_at) { is_expected.to have_formatting("2022-01-22T17:00:00Z").with_object(date) }
endhave_key(key)
Test that the exposed attribute uses an alias.
class Entity < Grape::Entity
expose :to_param, as: :id
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :to_param do
it { is_expected.to have_key :id }
end
# Or
its_exposure(:to_param) { is_expected.to have_key :id }
endinclude_documentation(*docs)
Test that the exposed attribute has the included documentation.
class Entity < Grape::Entity
expose :id, documentation: { type: Integer, desc: "Entity id" }
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :id do
it { is_expected.to have_documentation :type, :desc }
it { is_expected.to have_documentation :type }
it { is_expected.to have_documentation :desc }
it { is_expected.to have_documentation type: Integer }
it { is_expected.to have_documentation desc: "Entity id" }
it { is_expected.to have_documentation type: Integer, desc: "Entity id" }
end
# Or
its_exposure(:id) { is_expected.to have_documentation :type, :desc }
its_exposure(:id) { is_expected.to have_documentation :type }
its_exposure(:id) { is_expected.to have_documentation :desc }
its_exposure(:id) { is_expected.to have_documentation type: Integer }
its_exposure(:id) { is_expected.to have_documentation desc: "Entity id" }
its_exposure(:id) { is_expected.to have_documentation type: Integer, desc: "Entity id" }
endoverride_exposure
Test that the exposed attribute uses an alias.
class BaseEntity < Grape::Entity
expose :id
end
class Entity < BaseEntity
expose :id, override: true do |instance, options|
#...
end
end
RSpec.describe Entity, type: :grape_entity do
describe_exposure :id do
it { is_expected.to override_exposure }
end
# Or
its_exposure(:to_param) { is_expected.to override_exposure }
endDevelopment
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.
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.
Report Issues
Bug reports and pull requests are welcome on GitHub at https://github.com/jefawks3/rspec-grape-entity.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request
License
The gem is available as open source under the terms of the MIT License.