The GitHub API example
GitHub provides an extensive API. For instance, you can list the watchers of a repository with:
GET https://api.github.com/repos/:owner/:repo/subscribers
What response should you expect? According to the documentation, one example of possible response is:
Status: 200 OK
Link: <https://api.github.com/resource?page=2>; rel="next",
<https://api.github.com/resource?page=5>; rel="last"
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999
[
{
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "somehexcode",
"url": "https://api.github.com/users/octocat"
}
]
What promises is this example-based documentation making to the API consumers?
If you had to parse the result based solely on one example, you would be filled with doubts like:
- will the status code always be
200? - will the body always return a JSON
array? - will the array always include only one element?
- will each element always have a
loginattribute with the value"octocat"? - will the
idattribute always have anintegervalue? - will the
avatar_urlattribute always have a value?
The power of RSpecApi to the rescue
RSpecApi provides a new, concise and readable way to document the promises of the endpoint above:
resource :watcher do
host 'https://api.github.com'
authorize_with 'YOUR-GITHUB-PERSONAL-API-TOKEN'
has_attribute :login, type: :string
has_attribute :id, type: :number
has_attribute :avatar_url, type: [:null, {string: :url}]
has_attribute :gravatar_id, type: [:null, :string]
has_attribute :url, type: {string: :url}
get '/repos/:owner/:repo/subscribers', collection: true do
respond_with :ok, owner: existing(:user), repo: existing(:repo)
respond_with :not_found, owner: unknown(:user), repo: unknown(:repo)
end
end
This snippet of code is a more comprehensive documentation than an example-based one.
Even though the returned data from the endpoint might differ from call to call, the consumer can always expect:
- a status code of
200when given an existing owner/repo - a body of
JSON content-type - the body to be an
arrayof items, each representing a watcher - each watcher to have a
loginattribute with astringvalue - each watcher to have an
idattribute with annumericvalue - each watcher to have an
avatar_urlattribute with either aURLor anilvalue - each watcher to have a
gravatar_idattribute with either astringor anilvalue - each watcher to have a
urlattribute with aURLvalue - a status code of
404when given an unknown owner/repo
The best part of RSpecApi is that you can actually verify that GitHub is keeping its API promises at any given time, by executing the snippet of code above through RSpec and ensuring everything is green:
$ rspec spec/features/remote/github/activity/watchers_spec.rb
Watchers
GET https://api.github.com/repos/:owner/:repo/subscribers
given an existing owner "rspec-api" and an existing repo "rspec-api"
responds with a status code that
should be 200
responds with headers that
should include 'Content-Type': 'application/json; charset=utf-8'
responds with a body that
should be a JSON Array
should include the attribute "login" with a string value
should include the attribute "id" with an integer value
should include the attribute "avatar_url" with a URL or nil value
should include the attribute "gravatar_id" with a string or nil value
should include the attribute "url" with a URL value
given an unknown owner "i-don-t-exist" and an unknown repo "not-a-repo"
responds with a status code that
should be 404
Now, go and run this code for yourself, then tell me what you think. All you need is a GitHub API key (instructions).