├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib └── rspec │ ├── api_helpers.rb │ └── api_helpers │ ├── adapter │ ├── active_model │ │ ├── adapter.rb │ │ ├── collection.rb │ │ ├── common_helpers.rb │ │ └── resource.rb │ └── jsonapi │ │ └── adapter.rb │ ├── dispatcher.rb │ ├── example_group_methods.rb │ ├── example_methods.rb │ └── version.rb └── rspec-api_helpers.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | *.gem 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rspec-api.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Filippos Vasilakis 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rspec::ApiHelpers 2 | 3 | Usefull Rspec helpers for APIs (currently JSONAPI and AM-JSON adapters are supported) 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem 'rspec-api_helpers', '1.0.3' 11 | ``` 12 | 13 | And then execute: 14 | 15 | $ bundle 16 | 17 | Or install it yourself as: 18 | 19 | $ gem install rspec-api_helpers 20 | 21 | ## Usage 22 | This Gem expects you to have set your rspec to use Rake::Test helpers as described 23 | [here](https://gist.github.com/alex-zige/5795358) because it checks `last_response` 24 | attributes. 25 | 26 | In your `rails_helper.rb` add in the top: 27 | 28 | ```ruby 29 | require 'rspec/api_helpers' 30 | ``` 31 | 32 | Then you need to specify the adapter you want. 33 | 34 | ```ruby 35 | RSpec.configure do |config| 36 | config.include Rspec::ApiHelpers.with(adapter: :json_api) 37 | end 38 | ``` 39 | Other possible options for the adapter is `active_model`. 40 | 41 | You can also inject your custom made class by providing the class: 42 | ```ruby 43 | RSpec.configure do |config| 44 | config.include Rspec::ApiHelpers.with(adapter: Adapter::Hal) 45 | end 46 | ``` 47 | 48 | ### Examples 49 | The library heavily uses dynamic scopes through procs (an alternative to eval). 50 | 51 | 52 | #### General 53 | 54 | ```ruby 55 | it_returns_status(200) 56 | ``` 57 | It expects the HTTP response status to be 200. 58 | 59 | ```ruby 60 | it_includes_in_headers('SESSION_TOKEN' => proc{@user.token}) 61 | ``` 62 | It expects the HTTP response to include this header (and value). 63 | 64 | #### Resource 65 | 66 | ```ruby 67 | it_returns_attributes(resource: 'user', model: proc{@user}, attrs: [ 68 | :email, :name 69 | ]) 70 | ``` 71 | 72 | It expects the JSON resource to contain :email and :name attributes and 73 | be equal with with `@user` methods. 74 | 75 | 76 | ```ruby 77 | it_returns_attribute_values( 78 | resource: 'user', model: proc{@user}, attrs: [ 79 | :id, :name, :email, :admin, :activated, :created_at, :updated_at 80 | ], modifiers: { 81 | [:created_at, :updated_at] => proc{|i| i.iso8601}, 82 | :id => proc{|i| i.to_s} 83 | } 84 | ) 85 | ``` 86 | It expects the JSON resource to contain specific attribute values as above but for 87 | `:updated_at`, `:created_at` and `id` it applies specific methods first, defined in the 88 | `modifier` hash (note that the methods are applied in the JSON resource, not in the variable) 89 | 90 | ```ruby 91 | it_returns_no_attributes( 92 | resource: 'user', attrs: [:foo1, :foo2, :foo3] 93 | ) 94 | ``` 95 | It expects the JSON resource to NOT contain any of those attributes. 96 | 97 | #### Collection 98 | ```ruby 99 | it_returns_collection_size(resource: 'users', size: 6) 100 | ``` 101 | 102 | It expects the JSON collection to have `6` resources. 103 | 104 | ```ruby 105 | it_returns_collection_attributes( 106 | resource: 'users', attrs: [ 107 | :id, :name, :email, :admin, :activated, :created_at, :updated_at 108 | ] 109 | ) 110 | ``` 111 | 112 | It expects the JSON collection to have those attributes (no value checking). 113 | 114 | 115 | ```ruby 116 | it_returns_no_collection_attributes( 117 | resource: 'users', attrs: [ 118 | :foo 119 | ] 120 | ) 121 | ``` 122 | 123 | It expects the JSON collection NOT to have those attributes (no value checking). 124 | 125 | 126 | ```ruby 127 | it_returns_collection_attribute_values( 128 | resource: 'users', model: proc{User.first!}, attrs: [ 129 | :id, :name, :email, :admin, :activated, :created_at, :updated_at 130 | ], modifiers: { 131 | [:created_at, :updated_at] => proc{|i| i.iso8601}, 132 | :id => proc{|i| i.to_s} 133 | } 134 | ) 135 | ``` 136 | 137 | It expects the JSON collection to contain specific attribute values as above but for 138 | `:updated_at`, `:created_at` and `id` it applies specific methods first, defined in the 139 | `modifier` hash (note that the methods are applied in the JSON resource, not in the variable) 140 | 141 | 142 | ## To Do 143 | * Enhance documentation 144 | * Better dir structure (break example group methods to modules) 145 | * Support for nested resources 146 | * Add tests 147 | * Support for HAL/Siren adapter 148 | 149 | ## Contributing 150 | 151 | 1. Fork it ( https://github.com/[my-github-username]/rspec-api_helpers/fork ) 152 | 2. Create your feature branch (`git checkout -b my-new-feature`) 153 | 3. Commit your changes (`git commit -am 'Add some feature'`) 154 | 4. Push to the branch (`git push origin my-new-feature`) 155 | 5. Create a new Pull Request 156 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/api_helpers/version' 2 | require 'rspec/api_helpers/dispatcher' 3 | require 'rspec/api_helpers/example_methods' 4 | require 'rspec/api_helpers/example_group_methods' 5 | 6 | module Rspec 7 | 8 | module ApiHelpers 9 | class << self; 10 | attr_accessor :adapter 11 | end 12 | 13 | def self.included(receiver) 14 | receiver.extend ExampleGroupMethods 15 | receiver.send :include, ExampleMethods 16 | end 17 | 18 | def self.with(adapter:) 19 | if adapter.is_a?(Class) 20 | self.adapter = adapter 21 | else 22 | case adapter.to_s.to_sym 23 | when :active_model 24 | self.adapter = Adapter::ActiveModel 25 | when :json_api 26 | self.adapter = Adapter::JsonApi 27 | end 28 | end 29 | 30 | return self 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers/adapter/active_model/adapter.rb: -------------------------------------------------------------------------------- 1 | class Adapter 2 | class ActiveModel 3 | include RSpec::Matchers 4 | 5 | class << self 6 | def resource(options, response, _binding) 7 | hashed_response = HashWithIndifferentAccess.new(JSON.parse(response.body)) 8 | 9 | return Resource.new(options, hashed_response, _binding) 10 | end 11 | 12 | def collection(options, response, _binding) 13 | hashed_response = HashWithIndifferentAccess.new(JSON.parse(response.body)) 14 | 15 | return Collection.new(options, hashed_response, _binding) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers/adapter/active_model/collection.rb: -------------------------------------------------------------------------------- 1 | class Adapter::ActiveModel::Collection 2 | include Adapter::ActiveModel::CommonHelpers 3 | include RSpec::Matchers 4 | 5 | def initialize(options, response, _binding) 6 | @options = HashWithIndifferentAccess.new(options) 7 | @response = response 8 | @_binding = _binding 9 | end 10 | 11 | def has_size(size) 12 | expect(collection.size).to(eq size) 13 | end 14 | 15 | def sample(new_options = {}) 16 | Adapter::ActiveModel::Resource.new( 17 | options.merge(new_options), collection.sample.hash, @_binding 18 | ) 19 | end 20 | 21 | def find_by(id, new_options = {}) 22 | resource = collection.find{|i| i.send(id).to_s == model.send(id).to_s} 23 | if resource.nil? 24 | raise "Resource was not found for id: #{id} (model value: #{model.send(id)})" 25 | end 26 | 27 | return Adapter::ActiveModel::Resource.new( 28 | options.merge(new_options), resource.hash, @_binding 29 | ) 30 | end 31 | 32 | private 33 | attr_reader :options 34 | 35 | def model 36 | @model ||= parse_model( 37 | @_binding.instance_exec(&options[:model]) 38 | ) 39 | end 40 | 41 | def collection 42 | @collection ||= objectize_collection( 43 | @response, root: options[:resource] 44 | ) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers/adapter/active_model/common_helpers.rb: -------------------------------------------------------------------------------- 1 | module Adapter::ActiveModel::CommonHelpers 2 | def objectize_collection(collection, root: nil, existing: true) 3 | if root 4 | collection = collection[root] 5 | end 6 | 7 | return collection.map{|resource| 8 | object_hash(resource, existing: existing) 9 | } 10 | end 11 | 12 | def objectize_resource(resource, root: nil, existing: true) 13 | if root 14 | obj = object_hash(resource[root], existing: existing) 15 | else 16 | obj = object_hash(resource, existing: existing) 17 | end 18 | 19 | return obj 20 | end 21 | 22 | def object_hash(hash, existing: true) 23 | ObjectHash.new(hash, existing: existing) 24 | end 25 | 26 | class ObjectHash 27 | #existing denotes whether we search for attributes that exist on the 28 | #resource or attributes that shouldn't exist 29 | attr_accessor :hash, :existing 30 | def initialize(hash, existing: true) 31 | @hash = HashWithIndifferentAccess.new(hash) 32 | @existing = existing 33 | end 34 | 35 | def method_missing(name) 36 | if existing 37 | if hash.key?(name) 38 | return hash[name] 39 | else 40 | return raise KeyError.new("Attribute not found in resource: #{name}") 41 | end 42 | else 43 | if hash.key?(name) 44 | return raise( 45 | KeyError.new( 46 | "Attribute found in resource when it shouldn't: #{name}" 47 | ) 48 | ) 49 | else 50 | return :attribute_not_found 51 | end 52 | end 53 | end 54 | end 55 | 56 | def superset_mismatch_error(superset, subset) 57 | "Expected \n #{subset.to_a.to_s} \n to be included in \n #{superset.to_a.to_s}" 58 | end 59 | 60 | def parse_model(model) 61 | return model unless model.is_a? Hash 62 | 63 | return object_hash(model) 64 | end 65 | 66 | def modifier_for(key) 67 | if modifiers_hash[key] 68 | return modifiers_hash[key] 69 | else 70 | return proc{|i| i} 71 | end 72 | end 73 | 74 | end 75 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers/adapter/active_model/resource.rb: -------------------------------------------------------------------------------- 1 | class Adapter::ActiveModel::Resource 2 | include Adapter::ActiveModel::CommonHelpers 3 | include RSpec::Matchers 4 | 5 | def initialize(options, response, _binding) 6 | @options = HashWithIndifferentAccess.new(options) 7 | @response = response 8 | @_binding = _binding 9 | end 10 | 11 | def resource 12 | @resource ||= objectize_resource( 13 | @response, root: options[:resource], existing: options[:existing] 14 | ) 15 | end 16 | 17 | def compare_attribute(attribute) 18 | expect(resource.send(attribute)).to( 19 | eql( 20 | modifier_for(attribute).call( 21 | model.send(attribute) 22 | ) 23 | ) 24 | ) 25 | end 26 | 27 | def embedded_collection(new_options = {}) 28 | raise 'embeds option missing' unless (options[:embeds] || new_options[:embeds]) 29 | 30 | Adapter::ActiveModel::Collection.new( 31 | options.merge(new_options), resource.send(options[:embeds]), @_binding 32 | ) 33 | end 34 | 35 | def has_no_attribute(attribute) 36 | expect(resource.send(attribute)).to(eql(:attribute_not_found)) 37 | end 38 | 39 | def has_attribute(attribute) 40 | expect(resource.send(attribute)).to_not(eql(:attribute_not_found)) 41 | end 42 | 43 | private 44 | attr_reader :options 45 | 46 | def model 47 | raise 'model is missing' if options[:model].blank? 48 | 49 | @model ||= parse_model( 50 | @_binding.instance_exec(&options[:model]) 51 | ) 52 | end 53 | 54 | def attrs 55 | options[:attrs].map{|i| i.to_s} 56 | end 57 | 58 | def attrs? 59 | attrs.any? 60 | end 61 | 62 | def modifiers_hash 63 | return {} if options[:modifiers].blank? 64 | 65 | @modifiers_hash = HashWithIndifferentAccess.new({}) 66 | options[:modifiers].each do |key, value| 67 | [key].flatten.each do |attr| 68 | if attrs? 69 | raise "#{attr} missing from :attrs param" unless attrs.include?(attr.to_s) 70 | @modifiers_hash[attr] = value 71 | end 72 | end 73 | end 74 | 75 | return @modifiers_hash 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers/adapter/jsonapi/adapter.rb: -------------------------------------------------------------------------------- 1 | class Adapter 2 | class JsonApi 3 | include RSpec::Matchers 4 | 5 | class << self 6 | def resource(options, response, _binding) 7 | jsonapi_resource = HashWithIndifferentAccess.new(JSON.parse(response.body)) 8 | 9 | jsonapi_resource = transform_resource(jsonapi_resource.dig('data') || {}) 10 | 11 | return Adapter::ActiveModel::Resource.new(options, jsonapi_resource, _binding) 12 | end 13 | 14 | def collection(options, response, _binding) 15 | jsonapi_collection = HashWithIndifferentAccess.new(JSON.parse(response.body)) 16 | 17 | jsonapi_collection = transform_collection(jsonapi_collection.dig('data') || {}) 18 | 19 | return Adapter::ActiveModel::Collection.new(options, jsonapi_collection, _binding) 20 | end 21 | 22 | 23 | private 24 | def transform_resource(jsonapi_hash, root: true) 25 | type = jsonapi_hash.dig('type').singularize 26 | json_hash = jsonapi_hash.dig('attributes').transform_keys{|key| 27 | key.underscore 28 | }.merge(id: jsonapi_hash.dig('id').to_s) 29 | 30 | if root 31 | return HashWithIndifferentAccess.new({type => json_hash}) 32 | else 33 | return HashWithIndifferentAccess.new(json_hash) 34 | end 35 | end 36 | 37 | def transform_collection(jsonapi_hash, root: true) 38 | type = jsonapi_hash.first.dig('type').pluralize 39 | 40 | json_hash = jsonapi_hash.map{|data| 41 | transform_resource(data, root: false) 42 | } 43 | 44 | if root 45 | return HashWithIndifferentAccess.new({type => json_hash}) 46 | else 47 | return json_hash 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers/dispatcher.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/api_helpers/adapter/jsonapi/adapter' 2 | require 'rspec/api_helpers/adapter/active_model/adapter' 3 | require 'rspec/api_helpers/adapter/active_model/common_helpers' 4 | require 'rspec/api_helpers/adapter/active_model/resource' 5 | require 'rspec/api_helpers/adapter/active_model/collection' 6 | 7 | 8 | module Rspec 9 | module ApiHelpers 10 | class Dispatcher 11 | def adapter 12 | Rspec::ApiHelpers.adapter 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers/example_group_methods.rb: -------------------------------------------------------------------------------- 1 | module Rspec 2 | module ApiHelpers 3 | module ExampleGroupMethods 4 | def it_returns_status(status) 5 | it "returns the correct HTTP status (status: #{status})" do 6 | expect(last_response.status).to eql(status) 7 | end 8 | end 9 | 10 | #make rehashable 11 | def it_includes_in_headers(headers = {}) 12 | headers.each do |header, value| 13 | it "returns headers #{header} wih value: #{value}" do 14 | expect(last_response.headers[header.to_s]).to eq(eval(value)) 15 | end 16 | end 17 | end 18 | 19 | def it_returns_attribute_values(options = {}) 20 | it "expects returned resource (#{options[:resource]}) to have model attribute values" do 21 | resource = dispatcher.adapter.resource(options.merge(existing: true), last_response, self) 22 | 23 | #support proc on attrs 24 | options[:attrs].each do |attribute| 25 | begin 26 | resource.compare_attribute(attribute) 27 | rescue RSpec::Expectations::ExpectationNotMetError => e 28 | e.message << "failed at model attribute: #{attribute}" 29 | raise e 30 | end 31 | end 32 | end 33 | end 34 | alias_method :it_returns_more_attribute_values, :it_returns_attribute_values 35 | 36 | def it_returns_no_attributes(options = {}) 37 | it "expects returned resource (#{options[:root]}) to NOT have the following attributes" do 38 | resource = dispatcher.adapter.resource(options.merge(existing: false), last_response, self) 39 | 40 | options[:attrs].each do |attribute| 41 | begin 42 | resource.has_no_attribute(attribute) 43 | rescue RSpec::Expectations::ExpectationNotMetError => e 44 | e.message << "failed at model attribute: #{attribute}" 45 | raise e 46 | end 47 | end 48 | end 49 | end 50 | 51 | def it_returns_collection_size(options = {}) 52 | it "returns the correct number of resources in the #{options[:resource]} collection" do 53 | collection = dispatcher.adapter.collection(options.merge(existing: true), last_response, self) 54 | 55 | collection.has_size(options[:size]) 56 | end 57 | end 58 | 59 | def it_returns_collection_attributes(options = {}) 60 | it "returns the correct attributes (no value checking) for resources in the #{options[:resource]} collection" do 61 | sample_resource = dispatcher.adapter.collection( 62 | options, last_response, self 63 | ).sample(existing: true, resource: nil) 64 | 65 | #support proc on attrs 66 | options[:attrs].each do |attribute| 67 | begin 68 | sample_resource.has_attribute(attribute) 69 | rescue RSpec::Expectations::ExpectationNotMetError => e 70 | e.message << "failed at model attribute: #{attribute}" 71 | raise e 72 | end 73 | end 74 | end 75 | end 76 | alias_method( 77 | :it_returns_more_collection_attributes, 78 | :it_returns_collection_attributes 79 | ) 80 | 81 | def it_returns_no_collection_attributes(options = {}) 82 | it "expects returned collection (#{options[:resource]}) to NOT have the following attributes" do 83 | sample_resource = dispatcher.adapter.collection( 84 | options, last_response, self 85 | ).sample(existing: false, resource: nil) 86 | 87 | #support proc on attrs 88 | options[:attrs].each do |attribute| 89 | begin 90 | sample_resource.has_no_attribute(attribute) 91 | rescue RSpec::Expectations::ExpectationNotMetError => e 92 | e.message << "failed at model attribute: #{attribute}" 93 | raise e 94 | end 95 | end 96 | end 97 | end 98 | 99 | #finds_by id 100 | def it_returns_collection_attribute_values(options = {}) 101 | it "expects returned collection (#{options[:resource]}) to have model attribute values" do 102 | resource = dispatcher.adapter.collection( 103 | options, last_response, self 104 | ).find_by(:id, existing: true, resource: nil) 105 | 106 | #support proc on attrs 107 | options[:attrs].each do |attribute| 108 | begin 109 | resource.compare_attribute(attribute) 110 | rescue RSpec::Expectations::ExpectationNotMetError => e 111 | e.message << "failed at model attribute: #{attribute}" 112 | raise e 113 | end 114 | end 115 | end 116 | end 117 | 118 | =begin 119 | #not tested 120 | def it_returns_embedded_collection_size(options = {}) 121 | it "returns the correct number of embedded resource #{embeds} in the #{resource} resource" do 122 | collection = dispatcher.adapter.resource( 123 | options.merge(existing: true), last_response, self 124 | ).embedded_collection 125 | 126 | collection.has_size(options[:size]) 127 | end 128 | end 129 | 130 | def it_returns_collection_embedded_collection_size(options = {}) 131 | it "returns the correct number of embedded resource #{embeds} in the #{resource} collection" do 132 | 133 | end 134 | end 135 | 136 | def it_returns_collection_embedded_resource_attributes(options = {}) 137 | it "returns the correct attributes (no value checking) of #{embeds} resource inside #{resource} collection" do 138 | 139 | end 140 | end 141 | 142 | def it_returns_no_collection_embedded_resource_attributes(options = {}) 143 | it "expects the embedded resource #{embeds} inside the returned collection (#{resource}) to NOT have the following attributes" do 144 | 145 | end 146 | end 147 | =end 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers/example_methods.rb: -------------------------------------------------------------------------------- 1 | require "rspec/api_helpers/version" 2 | 3 | module Rspec 4 | module ApiHelpers 5 | module ExampleMethods 6 | def dispatcher 7 | @dispatcher ||= Rspec::ApiHelpers::Dispatcher.new 8 | end 9 | end 10 | end 11 | end 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/rspec/api_helpers/version.rb: -------------------------------------------------------------------------------- 1 | module Rspec 2 | module Api 3 | VERSION = "1.0.4" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /rspec-api_helpers.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'rspec/api_helpers/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "rspec-api_helpers" 8 | spec.version = Rspec::Api::VERSION 9 | spec.authors = ["Filippos Vasilakis", "Kollegorna"] 10 | spec.email = ["vasilakisfil@gmail.com", "admin@kollegorna.se"] 11 | spec.summary = %q{Rspec matchers for APIs} 12 | spec.description = %q{Rspec matchers for APIs} 13 | spec.homepage = "https://github.com/kollegorna/rspec-api_helpers" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_runtime_dependency "activesupport", "> 4.2" 22 | spec.add_runtime_dependency "rspec-core", "> 3.1" 23 | spec.add_development_dependency "bundler", "~> 1.7" 24 | spec.add_development_dependency "rake", "~> 10.0" 25 | end 26 | --------------------------------------------------------------------------------