diff --git a/Gemfile.lock b/Gemfile.lock index 0b7e511..f07412d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,12 @@ PATH remote: . specs: - rspec-api-requests (0.6.2) + rspec-api-requests (0.7.0) activesupport faraday faraday_middleware rspec - rspec-api-expectations (~> 0.6.2) + rspec-api-expectations (~> 0.7.0) GEM remote: https://rubygems.org/ @@ -46,13 +46,13 @@ GEM rspec-core (~> 2.14.0) rspec-expectations (~> 2.14.0) rspec-mocks (~> 2.14.0) - rspec-api-expectations (0.6.2) + rspec-api-expectations (0.7.0) activesupport faraday rack rspec - rspec-api-matchers (~> 0.6.1) - rspec-api-matchers (0.6.1) + rspec-api-matchers (~> 0.7.0) + rspec-api-matchers (0.7.0) activesupport rack rspec diff --git a/README.md b/README.md index 7e34533..134c616 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ RSpec API Requests ================== -RSpecApi::Requests lets make requests to web APIs and verify their expectations: +RSpecApi::Requests lets you make requests to web APIs and verify their expectations: params = {host: 'http://api.example.com', route: '/concerts', attributes: {where: {type: :string}} @@ -17,91 +17,165 @@ More documentation and examples about RSpecApi are available at [http://rspec-ap [![Dependency Status](https://gemnasium.com/rspec-api/rspec-api-requests.png)](https://gemnasium.com/rspec-api/rspec-api-requests) +Basic example +------------- + + # 1. Specify how to connect to the API and what to expect: + gists_params = { + host: 'https://api.github.com', + action: 'get', + authorize_with: {token: YOUR-GITHUB-TOKEN-HERE}, + route: '/gists', + collection: true, + attributes: {url: {type: {string: :url}}}, + extra_requests: [{ + expect: {filter: {by: :updated_at, compare_with: :>=, value: '2012-10-10T00:00:00Z'}}, + params: {since: '2012-10-10T00:00:00Z'} + }] + } + + # 2. Use `respond_to` to send the request and verify the expectations + describe 'GitHub Gists', rspec_api_params: gists_params do + respond_with :ok + end +Running the example above returns the following successful output: + Examples from GitHub API + GitHub Gists + GET /gists with {:since=>"2012-10-10T00:00:00Z"} + responds with a body that + should be a collection + should have attributes {:url=>{:type=>{:string=>:url}}} + should be filtered by updated_at>=2012-10-10T00:00:00Z + responds with a status code that + should be 200 + GET /gists + responds with a body that + should have attributes {:url=>{:type=>{:string=>:url}}} + should be a collection + responds with a status code that + should be 200 -**DRAFT! Work in Progress! Caution!** + Finished in 0.01056 seconds + 7 examples, 0 failures -Remember how RSpecApi::Expectations let you write something like this? +Note that, in order run the example, above, you need to replace +`YOUR-GITHUB-TOKEN-HERE` with your [GitHub Personal Access Token](https://github.com/settings/applications). - # 1. Make a request to the GitHub API: - require 'faraday' - conn = Faraday.new 'https://api.github.com/' do |c| - c.use Faraday::Adapter::NetHttp - end - conn.headers[:user_agent] = 'RSpec API' - conn.authorization :token, ENV['RSPEC_API_GITHUB_TOKEN'] - response = conn.get '/repositories' +Available request options +========================= - # 2. Expect the response to be successful and to return a JSON collection, - # where each object has an ID key/value (number), a name key/value - # (string) and a url key/value (string in URL format) +RSpecApi::Requests makes available one method `respond_to`, which sends the +request to the API and verify the expectations specified using RSpec metadata +`rspec_api_params` in the surrounding `describe` block, as shown above. +The following are the valid request options for `rspec_api_params`. - require 'rspec-api/requests' +:host and :adapter (*either one or the other is required*) +---------------------------------------------------------- - describe 'List all public repositories', rspec_api: true do - expect_response response, status: :ok, type: :json, collection: true, - attributes: {id: {type: :number}, name: {type: :string}, - url: {type: {string: :url}}} +Respectively the URL where the API is hosted and the adapter to connect to it. +By default, RSpecApi::Requests uses `Net::HTTP`, e.g: + host: 'http://example.com' # => connect via Net::HTTP -Well, it would make sense for that request and that expectation to go together, -not to be separate. RSpecApi::Requests let you do that, and write: +This behavior can be overridden specifying a different adapter: - describe 'List all public repositories', rspec_api: true do - get '/repositories', - host: 'https://api.github.com/', - authorize_with: ENV['RSPEC_API_GITHUB_TOKEN'], - attributes: {id: {type: :number}, name: {type: :string}, url: {type: {string: :url}}} do - respond_with :ok - end - end + adapter: [:rack, app] # => connect via Rack::Test to a local Rack app -or even this: +Therefore, RSpecApi::Requests can be used both to send requests to **remote** +and to **local** web API, making API development easier. - describe 'List all public repositories', rspec_api: true do - host 'https://api.github.com/' - authorize_with: {token: ENV['RSPEC_API_GITHUB_TOKEN']} +:route (*required*) +------------------- - has_attribute :id, {type: :number} - has_attribute :name, {type: :string}, url: {type: {string: :url}}} +The API route to access a resource, e.g.: - get '/repositories' do - respond_with :ok - end - end + host: 'http://example.com', route: '/concerts' + # => connect to 'http://example.com/concerts' + +:action (*required*) +-------------------- + +The HTTP method to access a resource, e.g.: + + action: :delete, route: '/concerts' + # => sends 'DELETE /concerts' + +:authorize_with (*optional*) +---------------------------- + +The HTTP authentication payload to send with the request, e.g.: + + authorize_with: {token: 'foo'} + # => sends [:token, 'foo'] as the HTTP authentication + + +Available expectations +====================== + +The following are the valid expectations for `rspec_api_params`. + +:collection (*optional*) +------------------------ + +Whether the response body will include a *single* JSON object +(`collection: false`) or a *collection* of objects (`collection: true`), e.g.: + + action: :get, route: '/concerts', collection: true + # => expect `GET /concerts' to return a collection + + action: :get, route: '/concerts/1', collection: false + # => expect `GET /concerts/1' to return an object + +:attributes (*optional*) +------------------------ + +Which attributes the JSON object or collection in the response body will contain, e.g.: + + action: :get, route: '/concerts/1', attributes: {url: {type: {string: :url}}} + # => expect `GET /concerts/1' to return an object with a 'url' field + # that should contain a URL-formatted string + +Extra requests +============== + +The last option of `rspec_api_params` allows to send multiple API requests: + +:extra_requests (*optional*) +---------------------------- + +Which extra requests should be sent and what to expect from them, e.g.: + + action: :get, route: '/concerts', extra_requests: [{ + expect: {filter: {by: :created_at, compare_with: :>=, value: '2012-10-10T00:00:00Z'}}, + params: {since: '2012-10-10T00:00:00Z'} + }] + # => expect `GET /concerts/1?since=2012-10-10T00:00:00Z' to only return + # concerts created after October 10th, 2012. -And all of these are equivalent. But this syntax hides the real requests, making -you focus more on the expectations, and also allows you to make many requests -with the same expectations on the attributes, which is a best practice of -an API. For instance if you get ONE repo, ALL the repos or FILTERED repos, -you should still get a result with those attributes: - get '/repositories?since=1234' do - respond_with :ok - end +How to install +============== - get '/repos/:owner/:repo' do - respond_with :ok, owner: 'rspec-api', repo: 'rspec-api-requests' - end +To install on your system, run `gem install rspec-api-requests`. +To use inside a bundled Ruby project, add this line to the Gemfile: - get '/repos/:owner/:repo' do - respond_with :not_found, owner: 'not-an-owner', repo: 'not-a-repo' - end + gem 'rspec-api-requests', '~> 0.7.0' -So in short, rspec-api-requests adds methods to RSpec's ExampleGroups tagged -as :rspec_api: +The rspec-api-requests gem follows [Semantic Versioning](http://semver.org). +Any new release that is fully backward-compatible bumps the *patch* version (0.0.x). +Any new version that breaks compatibility bumps the *minor* version (0.x.0) -* the most important methods are the actions: get, put, post, patch, -delete -- they make the request to the API +Indicating the full version in your Gemfile (*major*.*minor*.*patch*) guarantees +that your project won’t occur in any error when you `bundle update` and a new +version of RSpecApi::Requests is released. -* inside those methods, `respond_with`, which checks that the expectations on -that endpoint are met. By default, we expect JSON and we indicate the status. -Also, `respond_with` gets the values to replace the placeholders in the route -* more expectations on the attributes can be set with `has_attribute` +How to contribute +================= -* configuration about the requests can be set with `host` and `authorize_with` +Don’t hesitate to send me code comments, issues or pull requests through GitHub! -* extra requests can be run with `accepts` \ No newline at end of file +All feedback is appreciated. Thanks :) \ No newline at end of file diff --git a/lib/rspec-api/requests/version.rb b/lib/rspec-api/requests/version.rb index 88720fd..c648878 100644 --- a/lib/rspec-api/requests/version.rb +++ b/lib/rspec-api/requests/version.rb @@ -1,5 +1,5 @@ module RSpecApi module Requests - VERSION = '0.6.2' + VERSION = '0.7.0' end end \ No newline at end of file diff --git a/rspec-api-requests.gemspec b/rspec-api-requests.gemspec index 2bba8a6..261ad25 100644 --- a/rspec-api-requests.gemspec +++ b/rspec-api-requests.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.required_rubygems_version = ">= 1.3.6" spec.add_dependency 'rspec' - spec.add_dependency 'rspec-api-expectations', '~> 0.6.2' # expect(200).to match_status(200) + spec.add_dependency 'rspec-api-expectations', '~> 0.7.0' # expect(200).to match_status(200) spec.add_dependency 'activesupport' # to .slice, etc spec.add_dependency 'faraday' # to send remote HTTP requests