Snapi
Section 10
Testing
Testing
-
App/Routing
-
Curator
-
Mapper
-
Serializer
App/Routing
This lives in the x_endpoint_spec.rb file.
RSpec.describe "Contacts Endpoint", :type => :feature do
describe "fetching the root collection" do
it "returns base collection response", :skip_auth do
get "/contacts"
expect(collection["href"]).to eq("http://example.org/v3/contacts")
end
end
describe "fetching specific objects" do
end
describe "creating objects" do
end
describe "updating objects" do
end
describe "destroying objects" do
end
describe "searching for objects" do
end
end
App/Routing
This lives in the x_endpoint_spec.rb file.
RSpec.describe "Contacts Endpoint", :type => :feature do
describe "fetching specific objects" do
it "returns 404 on missing" do
get "/contacts/1"
expect(last_response).to be_not_found
end
it "returns single requested object" do
team = create_team
member = create_auth_member(:team_id => team.id)
contact = create_contact(:member_id => member.id)
get "/contacts/#{contact.id}"
expect(items[0].id).to eq(contact.id)
end
end
end
helpers
rspec/support/object_creation_helpers.rb
This file defines all the helpers that you will be using to write specs.
i.e. create_team, create_contact, create_auth_member
If a specific function doesn't exist it will use meta programming to figure out the ruby object to create. i.e. create_sms_gateway
helpers
def create_team(attributes = {})
plan = attributes.delete(:plan) { FREE_PLAN }
defaults = {:sport_id => 1}
super(defaults.merge(attributes))
.tap { |t| assign_plan(t, :plan_id => plan.id) }
end
Collection+JSON
Collection+JSON is a JSON-based read/write hypermedia-type designed to support management and querying of simple collections.
It uses a JSON structure to contains things like collections, refs, templates, queries, and commands.
https://github.com/collection-json/spec
include magic
class ContactApp < BaseApp
include Omicron.curation(ContactCurator)
include Omicron.serialization(ContactSerializer)
get "/contacts" do
pipe(default_response, :through => [
:serialize
])
end
get "/contacts/search" do
response = curate(artifact, :action => :search)
serialize(response)
end
end
We use the include magic to
inject serialize into the app.
class Response
include Virtus.value_object
values do
attribute :status, Integer, :default => 200
attribute :headers, Hash, :default => {}
attribute :collection, Array, :default => []
attribute :original_collection, Array, :default => []
attribute :page_information, Hash
attribute :error_message, String
end
end
include magic
Omicron.serialization takes the response and will return one of three things:
- Errors if response.error_message
- "" if no response.no_content?
- Call serialize on Serializer and return JSON
class ContactApp < BaseApp
include Omicron.serialization(ContactSerializer)
get "/contacts/search" do
response = curate(artifact, :action => :search)
serialize(response)
end
end
Scribe
All serializers inherit from Scribe which deals with the details of serialization. This allows you to only need a few functions in your serializer.
- search_url
- base_collection
- as_item
Serializer
class ContactSerializer < Scribe
private
def search_url(*args)
search_contacts_url(*args)
end
def base_collection
...
end
def as_item(object)
...
end
end
search_url
class ContactSerializer < Scribe
private
def search_url(*args)
search_contacts_url(*args)
end
end
base_collection
Returned for every request. Contains:
-
version -- the current version of the api
-
href -- href of this collection
-
rel -- unique identifier
-
template -- for creating/updating
-
links -- associated links
-
queries -- list of queries
-
commands -- list of commands
class ContactSerializer < Scribe
private
def base_collection
{
collection: {
version: API_VERSION,
href: contacts_url,
rel: "contacts",
template: {
data: [
{name: "label", value: nil},
{name: "type", value: Contact.type}
]
},
links: [
{rel: "contact_email_addresses", href: contact_email_addresses_url},
{rel: "self", href: request_url}
],
queries: [
{
rel: "search",
href: search_contacts_url,
data: [
{name: "team_id", value: nil},
{name: "can_receive_push_notifications", value: nil}
]
}
],
commands: [
{
rel: "create_bulk_contacts",
href: create_bulk_contacts_contacts_url,
data: [
{name: "team_id", value: nil},]
]
}
]
}
}
end
end
as_item
Called for every item in response.collection.
-
href -- href of this collection
-
data -- data for the item
-
links -- associated links
class ContactSerializer < Scribe
private
def as_item(object)
{
href: contact_url(object.id),
data: [
{name: "id", value: object.id},
{name: "type", value: object.type},
{name: "address_city", value: object.address_city},
{name: "address_country", value: object.address_country},
{name: "address_state", value: object.address_state},
{name: "address_street1", value: object.address_street1},
{name: "address_street2", value: object.address_street2},
{name: "address_zip", value: object.address_zip}
{name: "created_at", value: object.created_at, type: "DateTime"},
{name: "updated_at", value: object.updated_at, type: "DateTime"},
],
links: [
{
rel: "contact_email_addresses",
href: search_contact_email_addresses_url(:contact_id => object.id)
},
{
rel: "contact_phone_numbers",
href: search_contact_phone_numbers_url(:contact_id => object.id)
},
{rel: "member", href: member_url(object.member_id)},
]
}
end
end
{
"collection": {
"version": "3.454.0",
"href": "http://localhost:3000/contacts",
"rel": "contacts",
"template": {
"data": [
{
"name": "label",
"value": null
},
{
"name": "address_street1",
"value": null
},
...
{
"name": "type",
"value": "contact"
}
]
},
"links": [
{
"rel": "contact_email_addresses",
"href": "http://localhost:3000/contact_email_addresses"
},
....
{
"rel": "self",
"href": "http://localhost:3000/contacts"
}
],
"queries": [
{
"rel": "search",
"href": "http://localhost:3000/contacts/search",
"data": [
{
"name": "team_id",
"value": null
},
]
}
]
}
}
/contacts
{
"collection": {
"version": "3.454.0",
"href": "http://localhost:3000/contacts",
"rel": "contacts",
"template": {
...
},
"links": [
....
],
"queries": [
...
],
"items": [
{
"href": "http://localhost:3000/contacts/1",
"data": [
{
"name": "id",
"value": 1
},
{
"name": "type",
"value": "contact"
},
{
"name": "member_id",
"value": 63
},
{
"name": "created_at",
"value": "2017-05-10T17:28:05Z",
"type": "DateTime"
},
],
"links": [
{
"rel": "contact_email_addresses",
"href": "http://localhost:3000/contact_email_addresses/search?contact_id=1"
},
],
"rel": "contact-1"
}
]
}
}
/contacts/1
Serializer
Responsibilities
-
Converts response into Collection JSON
https://github.com/collection-json/spec
Thank you!
Snapi Section 10 Testing
By Dustin McCraw
Snapi Section 10 Testing
- 842