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
endApp/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
endhelpers
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
endWe 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
endScribe
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
endsearch_url
class ContactSerializer < Scribe
private
def search_url(*args)
search_contacts_url(*args)
end
endbase_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
endas_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
- 1,004