├── .gitignore ├── .rspec ├── .rvmrc.example ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── History.markdown ├── LICENSE ├── README.markdown ├── Rakefile ├── TODO.markdown ├── api_matchers.gemspec ├── lib ├── api_matchers.rb └── api_matchers │ ├── core │ ├── exceptions.rb │ ├── find_in_json.rb │ ├── parser.rb │ ├── rspec_matchers.rb │ └── setup.rb │ ├── headers │ ├── base.rb │ ├── be_json.rb │ └── be_xml.rb │ ├── http_status_code │ ├── base.rb │ ├── be_bad_request.rb │ ├── be_forbidden.rb │ ├── be_internal_server_error.rb │ ├── be_not_found.rb │ ├── be_ok.rb │ ├── be_unauthorized.rb │ ├── be_unprocessable_entity.rb │ └── create_resource.rb │ ├── response_body │ ├── base.rb │ ├── have_json.rb │ ├── have_json_node.rb │ ├── have_node.rb │ └── have_xml_node.rb │ └── version.rb └── spec ├── api_matchers ├── core │ ├── find_in_json_spec.rb │ └── setup_spec.rb ├── headers │ ├── base_spec.rb │ ├── be_json_spec.rb │ └── be_xml_spec.rb ├── http_status_code │ ├── base_spec.rb │ ├── be_bad_request_spec.rb │ ├── be_forbidden_spec.rb │ ├── be_internal_server_error_spec.rb │ ├── be_not_found_spec.rb │ ├── be_ok_spec.rb │ ├── be_unauthorized_spec.rb │ ├── be_unprocessable_entity_spec.rb │ └── create_resource_spec.rb └── response_body │ ├── base_spec.rb │ ├── have_json_node_spec.rb │ ├── have_json_spec.rb │ ├── have_node_spec.rb │ └── have_xml_node_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | InstalledFiles 7 | _yardoc 8 | coverage 9 | doc/ 10 | lib/bundler/man 11 | pkg 12 | rdoc 13 | spec/reports 14 | test/tmp 15 | test/version_tmp 16 | tmp 17 | *.DS_Store 18 | .rvmrc 19 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color -------------------------------------------------------------------------------- /.rvmrc.example: -------------------------------------------------------------------------------- 1 | rvm 1.9.2@api_matchers --create -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | script: 3 | - bundle exec rspec spec 4 | rvm: 5 | - rbx-2 6 | - jruby-head 7 | - ruby-head 8 | - ruby 9 | - jruby 10 | - 2.1.2 11 | - 2.0.0 12 | - 1.9.3 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | # Specify your gem's dependencies in api_matchers.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | api_matchers (0.6.2) 5 | activesupport (>= 3.2.5) 6 | nokogiri (~> 1.6.6.4) 7 | rspec (>= 3.1) 8 | 9 | GEM 10 | remote: http://rubygems.org/ 11 | specs: 12 | activesupport (4.2.2) 13 | i18n (~> 0.7) 14 | json (~> 1.7, >= 1.7.7) 15 | minitest (~> 5.1) 16 | thread_safe (~> 0.3, >= 0.3.4) 17 | tzinfo (~> 1.1) 18 | diff-lcs (1.2.5) 19 | i18n (0.7.0) 20 | json (1.8.3) 21 | mini_portile (0.6.2) 22 | minitest (5.7.0) 23 | nokogiri (1.6.6.4) 24 | mini_portile (~> 0.6.0) 25 | rspec (3.3.0) 26 | rspec-core (~> 3.3.0) 27 | rspec-expectations (~> 3.3.0) 28 | rspec-mocks (~> 3.3.0) 29 | rspec-core (3.3.0) 30 | rspec-support (~> 3.3.0) 31 | rspec-expectations (3.3.0) 32 | diff-lcs (>= 1.2.0, < 2.0) 33 | rspec-support (~> 3.3.0) 34 | rspec-mocks (3.3.0) 35 | diff-lcs (>= 1.2.0, < 2.0) 36 | rspec-support (~> 3.3.0) 37 | rspec-support (3.3.0) 38 | thread_safe (0.3.5) 39 | tzinfo (1.2.2) 40 | thread_safe (~> 0.1) 41 | 42 | PLATFORMS 43 | ruby 44 | 45 | DEPENDENCIES 46 | api_matchers! 47 | 48 | BUNDLED WITH 49 | 1.10.6 50 | -------------------------------------------------------------------------------- /History.markdown: -------------------------------------------------------------------------------- 1 | ## 0.6.2 2 | 3 | * Added RSpec 3.2.x and 3.3.x compatibility (Lucas Caton) 4 | 5 | ## 0.6.1 6 | 7 | * Add description to matchers. This makes RSpec not complain about missing 8 | description. 9 | 10 | ## 0.6.0 11 | 12 | * Add be_forbidden matcher: 13 | 14 | ```ruby 15 | expect(response.status).to be_forbidden 16 | ``` 17 | 18 | * Add RSpec 3 and RSpec 2 compatibility. 19 | 20 | ## 0.5.1 21 | 22 | * Fix #have_json matcher fail message. Fix issue #20 23 | 24 | ## 0.5.0 25 | 26 | * Internals - Migrate to the RSpec expect syntax. 27 | 28 | ## v0.4.0 29 | 30 | * jRuby support; 31 | * have_json matcher; 32 | 33 | ## v0.1.1 34 | 35 | 1) Support Datetime, Date and Time comparison (Thanks to Stephen Orens). 36 | 2) The have_node should accept boolean values (Thanks to Stephen Orens). 37 | 38 | ## v0.1.0 39 | 40 | 1) Add the #including_text for have_json_node and have_xml_node matcher: 41 | 42 | { :error => "Transaction error: Name can't be blank" }.to_json.should have_json_node(:error).including_text("Transaction error") 43 | 44 | "Transaction error: Name can't be blank".should have_xml_node(:error).including_text("Transaction error") 45 | 46 | ## v0.0.2 47 | 48 | 1) Put the headers method and the content type key in the setup class and that will be used by the headers matchers(be_json and be_xml). 49 | 50 | This: 51 | 52 | response.headers['Content-Type'].should be_in_json 53 | response.headers['Content-Type'].should be_in_xml 54 | 55 | With: 56 | 57 | APIMatchers.setup do |config| 58 | config.header_method = :headers 59 | config.header_content_type_key = 'Content-Type' 60 | end 61 | 62 | Becomes: 63 | 64 | response.should be_in_json 65 | response.should be_in_xml 66 | 67 | ## v0.0.1 68 | 69 | 1) Headers Matchers: be_xml, be_json (**OBS:** Need to think about the setup!) 70 | 2) HTTP Status Matchers: be_a_bad_request, be_internal_server_error, be_unauthorized, create_resource 71 | 3) Response body Matchers: have_node, have_json_node, have_xml_node 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Tomas D'Stefano 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. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # API Matchers [![Build Status](https://travis-ci.org/tomas-stefano/api_matchers.png?branch=master)](https://travis-ci.org/tomas-stefano/api_matchers) 2 | 3 | Collection of RSpec matchers for your API. 4 | 5 | ## Response Body Matchers 6 | 7 | * `have_node` 8 | * `have_json_node` 9 | * `have_xml_node` 10 | * `have_json` 11 | 12 | ## Response Status Matchers 13 | 14 | * `be_ok` 15 | * `create_resource` 16 | * `be_a_bad_request` 17 | * `be_unauthorized` 18 | * `be_forbidden` 19 | * `be_internal_server_error` 20 | * `be_not_found` 21 | 22 | ## Other Matchers 23 | 24 | * `be_in_xml` 25 | * `be_in_json` 26 | 27 | ## Install 28 | 29 | Include the gem to your test group in you Gemfile: 30 | 31 | ```ruby 32 | group :test do 33 | gem 'api_matchers' 34 | # other gems 35 | end 36 | ``` 37 | 38 | Or install it manually: `gem install api_matchers`. 39 | 40 | ## Usage 41 | 42 | ### Including in RSpec 43 | 44 | To include all this matchers you need to include the APIMatchers::RSpecMatchers module: 45 | 46 | ```ruby 47 | RSpec.configure do |config| 48 | config.include APIMatchers::RSpecMatchers 49 | end 50 | ``` 51 | 52 | ### Have Node Matcher 53 | 54 | The have_node matcher parse the actual and see if have the expcted node with the expected value. 55 | **The default that have_node will parse is JSON.** 56 | 57 | You can verify if node exists: 58 | 59 | ```ruby 60 | expect('{ "transaction": { "id": 54, "status": "paid" } }').to have_node(:transaction) 61 | ``` 62 | 63 | Or if node exist with a value: 64 | 65 | ```ruby 66 | expect('{ "transaction": { "id": 54, "status": "paid" } }').to have_node(:id).with(54) 67 | ``` 68 | 69 | ```ruby 70 | expect('{ "error": "not_authorized" }').to have_node(:error).with('not_authorized') 71 | ``` 72 | 73 | ```ruby 74 | expect('{"parcels":1 }').to have_node(:parcels).with(1) 75 | ``` 76 | 77 | To see the json node and see if include a text, you can do this: 78 | 79 | ```ruby 80 | expect('{"error": "Transaction error: Name cant be blank"}').to have_node(:error).including_text("Transaction error") 81 | ``` 82 | 83 | You can verify boolean values too: 84 | 85 | ```ruby 86 | expect('{"creditcard":true}').to have_node(:creditcard).with(true) 87 | ``` 88 | 89 | ### HAVE NODE Matcher Configuration 90 | 91 | You can configure if you want xml (JSON is the default): 92 | 93 | ```ruby 94 | APIMatchers.setup do |config| 95 | config.content_type = :xml 96 | end 97 | ``` 98 | 99 | ```ruby 100 | expect('200paid').to have_node(:status) 101 | ``` 102 | 103 | Using the `with` method: 104 | 105 | ```ruby 106 | expect('200paid').to have_node(:status).with('paid') 107 | ``` 108 | 109 | Or you can use the `have_xml_node` matcher: 110 | 111 | ```ruby 112 | expect( 113 | "Transaction error: Name can't be blank" 114 | ).to have_xml_node(:error).with("Transaction error: Name can't be blank") 115 | ``` 116 | 117 | To see the xml node and see if include a text, you can do this: 118 | 119 | ```ruby 120 | expect( 121 | "Transaction error: Name can't be blank" 122 | ).to have_xml_node(:error).including_text("Transaction error") 123 | ``` 124 | 125 | **If you work with xml and json in the same API, check the have_json_node and have_xml_node matchers.** 126 | 127 | You can configure the name of the method and then you will be able to use *without* the **#body** method, for example: 128 | 129 | ```ruby 130 | APIMatchers.setup do |config| 131 | config.response_body_method = :body 132 | end 133 | 134 | expect(response).to have_node(:foo).with('bar') 135 | ``` 136 | 137 | Instead of: 138 | 139 | ```ruby 140 | expect(response.body).to have_node(:foo) 141 | ``` 142 | 143 | ### Have JSON Node Matcher 144 | 145 | ```ruby 146 | expect( 147 | '{ "transaction": { "id": 54, "status": "paid" } }' 148 | ).to have_json_node(:id).with(54) 149 | ``` 150 | 151 | ### Have XML Node Matcher 152 | 153 | ```ruby 154 | expect("gateway").to have_xml_node(:name).with('gateway') 155 | ``` 156 | 157 | ### Have JSON Matcher 158 | 159 | Sometimes, you want to compare the entire JSON structure: 160 | 161 | ```ruby 162 | expect("['Foo', 'Bar', 'Baz']").to have_json(['Foo', 'Bar', 'Baz']) 163 | ``` 164 | 165 | ### Create Resource Matcher 166 | 167 | This matchers see the HTTP STATUS CODE is equal to 201. 168 | 169 | ```ruby 170 | expect(response.status).to create_resource 171 | ``` 172 | 173 | ### BAD REQUEST Matcher 174 | 175 | This BAD REQUEST is a matcher that see if the HTTP STATUS code is equal to 400. 176 | 177 | ```ruby 178 | expect(response.status).to be_a_bad_request 179 | expect(response.status).to be_bad_request 180 | ``` 181 | 182 | ### UNAUTHORIZED Matcher 183 | 184 | This UNAUTHORIZED is a matcher that see if the HTTP STATUS code is equal to 401. 185 | 186 | ```ruby 187 | expect(response.status).to be_unauthorized 188 | expect(response.body).to have_node(:message).with('Invalid Credentials') 189 | ``` 190 | 191 | ### FORBIDDEN Matcher 192 | 193 | This is a matcher to see if the HTTP STATUS code is equal to 403. 194 | 195 | ```ruby 196 | expect(response.status).to be_forbidden 197 | ``` 198 | 199 | ### INTERNAL SERVER ERROR Matcher 200 | 201 | This INTERNAL SERVER Error is a matcher that see if the HTTP STATUS code is equal to 500. 202 | 203 | ```ruby 204 | expect(response.status).to be_internal_server_error 205 | expect( 206 | response.body 207 | ).to have_node(:message).with('An Internal Error Occurs in our precious app. :S') 208 | ``` 209 | 210 | ### HTTP STATUS CODE Configuration 211 | 212 | You can configure the name method to call the http status code: 213 | 214 | ```ruby 215 | APIMatchers.setup do |config| 216 | config.http_status_method = :status 217 | end 218 | ``` 219 | 220 | Then you can use without call the **#status** method: 221 | 222 | ```ruby 223 | expect(response).to create_resource 224 | ``` 225 | 226 | This configurations affects this matchers: 227 | 228 | * `be_ok` 229 | * `create_resource` 230 | * `be_a_bad_request` 231 | * `be_internal_server_error` 232 | * `be_unauthorized` 233 | * `be_forbidden` 234 | * `be_not_found` 235 | 236 | ### Be in XML Matcher 237 | 238 | This is a matcher that see if the content type is xml: 239 | 240 | ```ruby 241 | expect(response.headers['Content-Type']).to be_in_xml 242 | ``` 243 | 244 | ### Be in JSON Matcher 245 | 246 | This is a matcher that see if the content type is in JSON: 247 | 248 | ```ruby 249 | expect(response.headers['Content-Type']).to be_in_json 250 | ``` 251 | 252 | ### Headers Configuration 253 | 254 | You can configure the name method to call the headers and content type: 255 | 256 | ```ruby 257 | APIMatchers.setup do |config| 258 | config.header_method = :headers 259 | config.header_content_type_key = 'Content-Type' 260 | end 261 | ``` 262 | 263 | And then you will be able to use without call the **#headers** calling the **#['Content-Type']** method: 264 | 265 | ```ruby 266 | expect(response).to be_in_json 267 | expect(response).to be_in_xml 268 | ``` 269 | 270 | ### Acknowlegments 271 | 272 | * Special thanks to Daniel Konishi to contribute in the product that I extracted the matchers to this gem. 273 | 274 | ### Contributors 275 | 276 | * Stephen Orens 277 | * Lucas Caton 278 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | -------------------------------------------------------------------------------- /TODO.markdown: -------------------------------------------------------------------------------- 1 | * Refactor Core::FindInJSON#find method. 2 | * Improve the error messages from all matchers. 3 | * Adding some include matcher about node arrays. 4 | -------------------------------------------------------------------------------- /api_matchers.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/api_matchers/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Tomas D'Stefano"] 6 | gem.email = ["tomas_stefano@successoft.com"] 7 | gem.description = %q{Collection of RSpec matchers for create your API.} 8 | gem.summary = %q{Collection of RSpec matchers for create your API.} 9 | gem.homepage = "https://github.com/tomas-stefano/api_matchers" 10 | 11 | gem.files = `git ls-files`.split($\) 12 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 13 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 14 | gem.name = "api_matchers" 15 | gem.require_paths = ["lib"] 16 | gem.version = APIMatchers::VERSION 17 | 18 | gem.add_dependency 'rspec', '>= 3.1' 19 | gem.add_dependency 'activesupport', '>= 3.2.5' 20 | gem.add_dependency 'nokogiri', '~> 1.6.6.4' 21 | end 22 | -------------------------------------------------------------------------------- /lib/api_matchers.rb: -------------------------------------------------------------------------------- 1 | require "api_matchers/version" 2 | require "active_support/core_ext/object" 3 | require "active_support/core_ext/class" 4 | 5 | module APIMatchers 6 | autoload :RSpecMatchers, 'api_matchers/core/rspec_matchers' 7 | 8 | # HTTP Status Code Matchers 9 | # 10 | module HTTPStatusCode 11 | autoload :Base, 'api_matchers/http_status_code/base' 12 | autoload :BeBadRequest, 'api_matchers/http_status_code/be_bad_request' 13 | autoload :BeNotFound, 'api_matchers/http_status_code/be_not_found' 14 | autoload :BeInternalServerError, 'api_matchers/http_status_code/be_internal_server_error' 15 | autoload :BeUnauthorized, 'api_matchers/http_status_code/be_unauthorized' 16 | autoload :BeOk, 'api_matchers/http_status_code/be_ok' 17 | autoload :BeUnprocessableEntity, 'api_matchers/http_status_code/be_unprocessable_entity' 18 | autoload :BeForbidden, 'api_matchers/http_status_code/be_forbidden' 19 | autoload :CreateResource, 'api_matchers/http_status_code/create_resource' 20 | end 21 | 22 | # Content Type Matchers 23 | # 24 | module Headers 25 | autoload :Base, 'api_matchers/headers/base' 26 | autoload :BeXML, 'api_matchers/headers/be_xml' 27 | autoload :BeJSON, 'api_matchers/headers/be_json' 28 | end 29 | 30 | # Response Body Matchers 31 | # 32 | module ResponseBody 33 | autoload :Base, 'api_matchers/response_body/base' 34 | autoload :HaveJsonNode, 'api_matchers/response_body/have_json_node' 35 | autoload :HaveJson, 'api_matchers/response_body/have_json' 36 | autoload :HaveXmlNode, 'api_matchers/response_body/have_xml_node' 37 | autoload :HaveNode, 'api_matchers/response_body/have_node' 38 | end 39 | 40 | # Core 41 | # 42 | module Core 43 | autoload :FindInJSON, 'api_matchers/core/find_in_json' 44 | autoload :Parser, 'api_matchers/core/parser' 45 | autoload :Setup, 'api_matchers/core/setup' 46 | autoload :Exceptions, 'api_matchers/core/exceptions' 47 | end 48 | include ::APIMatchers::Core::Exceptions 49 | 50 | def self.setup 51 | yield(::APIMatchers::Core::Setup) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/api_matchers/core/exceptions.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module Core 3 | module Exceptions 4 | class KeyNotFound < StandardError 5 | end 6 | 7 | class InvalidJSON < StandardError 8 | end 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /lib/api_matchers/core/find_in_json.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module Core 3 | class FindInJSON 4 | attr_reader :json 5 | 6 | def initialize(json) 7 | @json = json 8 | end 9 | 10 | def find(options={}) 11 | expected_key = options.fetch(:node).to_s 12 | expected_value = options[:value] 13 | 14 | @json.each do |key, value| 15 | if key == expected_key 16 | unless expected_value.nil? 17 | if expected_value.is_a? DateTime or expected_value.is_a? Date 18 | expected_value = expected_value.to_s 19 | elsif expected_value.is_a? Time 20 | expected_value = expected_value.to_datetime.to_s 21 | end 22 | end 23 | return value if value == expected_value or expected_value.nil? 24 | end 25 | 26 | # do we have more to recurse through? 27 | keep_going = nil 28 | if value.is_a? Hash or value.is_a? Array 29 | keep_going = value # hash or array, keep going 30 | elsif value.nil? and key.is_a? Hash 31 | keep_going = key # the array was passed in and now in the key, keep going 32 | end 33 | 34 | if keep_going 35 | begin 36 | # ignore nodes where the key doesn't match 37 | return FindInJSON.new(keep_going).find(node: expected_key, value: expected_value) 38 | rescue ::APIMatchers::Core::Exceptions::KeyNotFound 39 | end 40 | end 41 | 42 | end 43 | # we did not find the requested key 44 | raise ::APIMatchers::Core::Exceptions::KeyNotFound.new("key was not found") 45 | end 46 | end 47 | end 48 | end -------------------------------------------------------------------------------- /lib/api_matchers/core/parser.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module Core 3 | module Parser 4 | def json 5 | JSON.parse(response_body) 6 | rescue JSON::ParserError => exception 7 | raise ::APIMatchers::InvalidJSON.new("Invalid JSON: '#{response_body}'") 8 | end 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /lib/api_matchers/core/rspec_matchers.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module RSpecMatchers 3 | def be_bad_request 4 | ::APIMatchers::HTTPStatusCode::BeBadRequest.new(::APIMatchers::Core::Setup) 5 | end 6 | alias :be_a_bad_request :be_bad_request 7 | 8 | def be_not_found 9 | ::APIMatchers::HTTPStatusCode::BeNotFound.new(::APIMatchers::Core::Setup) 10 | end 11 | 12 | def be_internal_server_error 13 | ::APIMatchers::HTTPStatusCode::BeInternalServerError.new(::APIMatchers::Core::Setup) 14 | end 15 | alias :be_an_internal_server_error :be_internal_server_error 16 | 17 | def be_unauthorized 18 | ::APIMatchers::HTTPStatusCode::BeUnauthorized.new(::APIMatchers::Core::Setup) 19 | end 20 | 21 | def be_forbidden 22 | ::APIMatchers::HTTPStatusCode::BeForbidden.new(::APIMatchers::Core::Setup) 23 | end 24 | 25 | def be_ok 26 | ::APIMatchers::HTTPStatusCode::BeOk.new(::APIMatchers::Core::Setup) 27 | end 28 | 29 | def be_unprocessable_entity 30 | ::APIMatchers::HTTPStatusCode::BeUnprocessableEntity.new(::APIMatchers::Core::Setup) 31 | end 32 | 33 | def create_resource 34 | ::APIMatchers::HTTPStatusCode::CreateResource.new(::APIMatchers::Core::Setup) 35 | end 36 | alias :created_resource :create_resource 37 | 38 | def be_xml 39 | ::APIMatchers::Headers::BeXML.new(::APIMatchers::Core::Setup) 40 | end 41 | alias :be_in_xml :be_xml 42 | 43 | def be_json 44 | ::APIMatchers::Headers::BeJSON.new(::APIMatchers::Core::Setup) 45 | end 46 | alias :be_in_json :be_json 47 | alias :be_a_json :be_json 48 | 49 | def have_json_node(expected_node) 50 | ::APIMatchers::ResponseBody::HaveJsonNode.new(expected_node: expected_node, setup: ::APIMatchers::Core::Setup) 51 | end 52 | 53 | def have_xml_node(expected_node) 54 | ::APIMatchers::ResponseBody::HaveXmlNode.new(expected_node: expected_node, setup: ::APIMatchers::Core::Setup) 55 | end 56 | 57 | def have_json(expected_json) 58 | ::APIMatchers::ResponseBody::HaveJson.new(expected_json) 59 | end 60 | 61 | def have_node(expected_node) 62 | if ::APIMatchers::Core::Setup.have_node_matcher.equal?(:json) 63 | have_json_node(expected_node) 64 | else 65 | have_xml_node(expected_node) 66 | end 67 | end 68 | end 69 | end -------------------------------------------------------------------------------- /lib/api_matchers/core/setup.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module Core 3 | class Setup 4 | # The http status method that will be called when you call http status matchers 5 | # 6 | # ==== Examples 7 | # 8 | # response.status.should create_resource 9 | # response.status.should be_bad_request 10 | # response.status.should be_unauthorized 11 | # 12 | # # Instead calling #status everytime, you can configure: 13 | # 14 | # APIMatchers.setup do |config| 15 | # config.http_status_method = :status 16 | # end 17 | # 18 | # Then: 19 | # 20 | # response.should create_resource 21 | # response.should be_bad_request 22 | # response.should be_unauthorized 23 | # 24 | cattr_accessor :http_status_method 25 | 26 | # The response body method that will be called when you call the have_node matchers 27 | # 28 | # ==== Examples 29 | # 30 | # response.body.should have_node(:foo) 31 | # response.body.should have_node(:bar) 32 | # response.body.should have_node(:baz) 33 | # 34 | # # Instead calling #body everytime, you can configure: 35 | # 36 | # APIMatchers.setup do |config| 37 | # config.http_status_method = :body 38 | # end 39 | # 40 | # Then: 41 | # 42 | # response.should have_node(:foo) 43 | # response.should have_node(:bar) 44 | # response.should have_node(:baz) 45 | # 46 | cattr_accessor :response_body_method 47 | 48 | # The default have node matcher that will be used. 49 | # This have_node matcher is useful when you just work with one content type in your API. 50 | # Change to :xml if you want that have_node works ONLY with XML. 51 | # If you work with xml and json in the same API, I recommend that you check the 52 | # have_json_node and have_xml_node matchers. 53 | # 54 | cattr_accessor :have_node_matcher 55 | self.have_node_matcher = :json 56 | 57 | # The headers method and the content type key that will be used by the headers matchers. 58 | # 59 | # ==== Examples 60 | # 61 | # response.response_header['Content-Type'].should be_json 62 | # response.response_header['Content-Type'].should be_xml 63 | # 64 | # # Instead calling #response_header everytime, you can configure: 65 | # 66 | # APIMatchers.setup do |config| 67 | # config.header_method = :response_header 68 | # config.header_content_type_key = 'Content-Type' 69 | # end 70 | # 71 | # Then: 72 | # 73 | # response.should be_json 74 | # response.should be_xml 75 | # 76 | cattr_accessor :header_method 77 | cattr_accessor :header_content_type_key 78 | end 79 | end 80 | end -------------------------------------------------------------------------------- /lib/api_matchers/headers/base.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module Headers 3 | class Base 4 | attr_reader :setup 5 | 6 | def initialize(setup) 7 | @setup = setup 8 | end 9 | 10 | def matches?(actual) 11 | @actual = actual 12 | 13 | content_type_response.eql?(expected_content_type) 14 | end 15 | 16 | def content_type_response 17 | if @setup.header_method.present? and @setup.header_content_type_key.present? 18 | headers = @actual.send(@setup.header_method) 19 | headers[@setup.header_content_type_key] || headers if headers.present? 20 | else 21 | @actual 22 | end 23 | end 24 | 25 | def expected_content_type 26 | raise NotImplementedError, "not implemented on #{self}" 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/api_matchers/headers/be_json.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module Headers 3 | class BeJSON < Base 4 | def expected_content_type 5 | 'application/json; charset=utf-8' 6 | end 7 | 8 | def failure_message 9 | %Q{expected a JSON response with '#{expected_content_type}'. Got: '#{content_type_response}'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected to not be a JSON response. Got: '#{expected_content_type}'.} 14 | end 15 | 16 | def description 17 | "be in JSON" 18 | end 19 | 20 | # RSpec 2 compatibility: 21 | alias_method :failure_message_for_should, :failure_message 22 | alias_method :failure_message_for_should_not, :failure_message_when_negated 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/api_matchers/headers/be_xml.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module Headers 3 | class BeXML < Base 4 | def expected_content_type 5 | 'application/xml; charset=utf-8' 6 | end 7 | 8 | def failure_message 9 | %Q{expected a XML response with '#{expected_content_type}'. Got: '#{content_type_response}'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected to not be a XML response. Got: '#{expected_content_type}'.} 14 | end 15 | 16 | def description 17 | "be in XML" 18 | end 19 | 20 | # RSpec 2 compatibility: 21 | alias_method :failure_message_for_should, :failure_message 22 | alias_method :failure_message_for_should_not, :failure_message_when_negated 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/api_matchers/http_status_code/base.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module HTTPStatusCode 3 | class Base 4 | attr_reader :setup 5 | 6 | def initialize(setup) 7 | @setup = setup 8 | end 9 | 10 | # Matches the actual with the expected http status code 11 | # 12 | def matches?(actual) 13 | @http_status_code = find_http_status_code(actual) 14 | @http_status_code.equal?(expected_status_code) 15 | end 16 | 17 | def expected_status_code 18 | raise NotImplementedError, "not implemented on #{self}" 19 | end 20 | 21 | # If have some configuration about the method to call on actual that method will be called. 22 | # 23 | def find_http_status_code(actual) 24 | if @setup.http_status_method.present? 25 | actual.send(@setup.http_status_method) 26 | else 27 | actual 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/api_matchers/http_status_code/be_bad_request.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module HTTPStatusCode 3 | class BeBadRequest < Base 4 | def expected_status_code 5 | 400 6 | end 7 | 8 | def failure_message 9 | %Q{expected that '#{@http_status_code}' to be a Bad Request with the status '400'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected that '#{@http_status_code}' to NOT be a Bad Request with the status '400'.} 14 | end 15 | 16 | def description 17 | "be a Bad Request with the status '400'" 18 | end 19 | 20 | # RSpec 2 compatibility: 21 | alias_method :failure_message_for_should, :failure_message 22 | alias_method :failure_message_for_should_not, :failure_message_when_negated 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/api_matchers/http_status_code/be_forbidden.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module HTTPStatusCode 3 | class BeForbidden < Base 4 | def expected_status_code 5 | 403 6 | end 7 | 8 | def failure_message 9 | %Q{expected that '#{@http_status_code}' to be Forbidden with the status '403'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected that '#{@http_status_code}' to NOT be Forbidden.} 14 | end 15 | 16 | # RSpec 2 compatibility: 17 | alias_method :failure_message_for_should, :failure_message 18 | alias_method :failure_message_for_should_not, :failure_message_when_negated 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /lib/api_matchers/http_status_code/be_internal_server_error.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module HTTPStatusCode 3 | class BeInternalServerError < Base 4 | def expected_status_code 5 | 500 6 | end 7 | 8 | def failure_message 9 | %Q{expected that '#{@http_status_code}' to be Internal Server Error with the status '500'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected that '#{@http_status_code}' to NOT be Internal Server Error.} 14 | end 15 | 16 | def description 17 | "be Internal Server Error with the status '500'" 18 | end 19 | 20 | # RSpec 2 compatibility: 21 | alias_method :failure_message_for_should, :failure_message 22 | alias_method :failure_message_for_should_not, :failure_message_when_negated 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/api_matchers/http_status_code/be_not_found.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module HTTPStatusCode 3 | class BeNotFound < Base 4 | def expected_status_code 5 | 404 6 | end 7 | 8 | def failure_message 9 | %Q{expected that '#{@http_status_code}' to be Not Found with the status '404'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected that '#{@http_status_code}' to NOT be Not Found with the status '404'.} 14 | end 15 | 16 | def description 17 | "be Not Found with the status '404'" 18 | end 19 | 20 | # RSpec 2 compatibility: 21 | alias_method :failure_message_for_should, :failure_message 22 | alias_method :failure_message_for_should_not, :failure_message_when_negated 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/api_matchers/http_status_code/be_ok.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module HTTPStatusCode 3 | class BeOk < Base 4 | def expected_status_code 5 | 200 6 | end 7 | 8 | def failure_message 9 | %Q{expected that '#{@http_status_code}' to be ok with the status '200'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected that '#{@http_status_code}' to NOT be ok with the status '200'.} 14 | end 15 | 16 | def description 17 | "be ok with the status '200'" 18 | end 19 | 20 | # RSpec 2 compatibility: 21 | alias_method :failure_message_for_should, :failure_message 22 | alias_method :failure_message_for_should_not, :failure_message_when_negated 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/api_matchers/http_status_code/be_unauthorized.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module HTTPStatusCode 3 | class BeUnauthorized < Base 4 | def expected_status_code 5 | 401 6 | end 7 | 8 | def failure_message 9 | %Q{expected that '#{@http_status_code}' to be Unauthorized with the status '401'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected that '#{@http_status_code}' to NOT be Unauthorized.} 14 | end 15 | 16 | def description 17 | "be Unauthorized with the status '401'" 18 | end 19 | 20 | # RSpec 2 compatibility: 21 | alias_method :failure_message_for_should, :failure_message 22 | alias_method :failure_message_for_should_not, :failure_message_when_negated 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/api_matchers/http_status_code/be_unprocessable_entity.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module HTTPStatusCode 3 | class BeUnprocessableEntity < Base 4 | def expected_status_code 5 | 422 6 | end 7 | 8 | def failure_message 9 | %Q{expected that '#{@http_status_code}' to be Unprocessable entity with the status '#{expected_status_code}'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected that '#{@http_status_code}' to NOT be Unprocessable entity with the status '#{expected_status_code}'.} 14 | end 15 | 16 | def description 17 | "be Unprocessable entity with the status '422'" 18 | end 19 | 20 | # RSpec 2 compatibility: 21 | alias_method :failure_message_for_should, :failure_message 22 | alias_method :failure_message_for_should_not, :failure_message_when_negated 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/api_matchers/http_status_code/create_resource.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module HTTPStatusCode 3 | class CreateResource < Base 4 | def expected_status_code 5 | 201 6 | end 7 | 8 | def failure_message 9 | %Q{expected that '#{@http_status_code}' to be Created Resource with the status '201'.} 10 | end 11 | 12 | def failure_message_when_negated 13 | %Q{expected that '#{@http_status_code}' to NOT be Created Resource.} 14 | end 15 | 16 | def description 17 | "be Created Resource with the status '201'" 18 | end 19 | 20 | # RSpec 2 compatibility: 21 | alias_method :failure_message_for_should, :failure_message 22 | alias_method :failure_message_for_should_not, :failure_message_when_negated 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/api_matchers/response_body/base.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module ResponseBody 3 | class Base 4 | attr_reader :setup, :expected_node, :actual 5 | attr_writer :actual 6 | 7 | def initialize(options={}) 8 | @expected_node = options.fetch(:expected_node) 9 | @setup = options.fetch(:setup) 10 | end 11 | 12 | def matches?(actual) 13 | raise NotImplementedError, "not implemented on #{self}" 14 | end 15 | 16 | def with(expected_value) 17 | @with_value = expected_value 18 | self 19 | end 20 | 21 | def including_text(expected_including_text) 22 | @expected_including_text = expected_including_text 23 | self 24 | end 25 | 26 | def response_body 27 | if @setup.response_body_method.present? 28 | @actual.send(@setup.response_body_method) 29 | else 30 | @actual 31 | end 32 | end 33 | 34 | def failure_message 35 | "expected to have node called: '#{@expected_node}'" << added_message << ". Got: '#{response_body}'" 36 | end 37 | 38 | def failure_message_when_negated 39 | "expected to NOT have node called: '#{@expected_node}'" << added_message << ". Got: '#{response_body}'" 40 | end 41 | 42 | # RSpec 2 compatibility: 43 | alias_method :failure_message_for_should, :failure_message 44 | alias_method :failure_message_for_should_not, :failure_message_when_negated 45 | 46 | def added_message 47 | if @with_value 48 | " with value: '#{@with_value}'" 49 | elsif @expected_including_text 50 | " including text: '#{@expected_including_text}'" 51 | else 52 | "" 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/api_matchers/response_body/have_json.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module ResponseBody 3 | class HaveJson 4 | include APIMatchers::Core::Parser 5 | attr_reader :expected_json, :response_body 6 | 7 | def initialize(expected_json) 8 | @expected_json = expected_json 9 | end 10 | 11 | def matches?(actual) 12 | @response_body = actual 13 | 14 | @expected_json == json 15 | end 16 | 17 | def failure_message 18 | "expect to have json: '#{expected_json}'. Got: '#{json}'." 19 | end 20 | 21 | def failure_message_when_negated 22 | "expect to NOT have json: '#{response_body}'." 23 | end 24 | 25 | # RSpec 2 compatibility: 26 | alias_method :failure_message_for_should, :failure_message 27 | alias_method :failure_message_for_should_not, :failure_message_when_negated 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/api_matchers/response_body/have_json_node.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'active_support/core_ext/hash' 3 | 4 | module APIMatchers 5 | module ResponseBody 6 | class HaveJsonNode < Base 7 | include APIMatchers::Core::Parser 8 | 9 | def matches?(actual) 10 | @actual = actual 11 | 12 | begin 13 | node = Core::FindInJSON.new(json).find(node: @expected_node.to_s, value: @with_value) 14 | 15 | if @expected_including_text 16 | node.to_s.include?(@expected_including_text) 17 | else 18 | true # the node is present 19 | end 20 | rescue ::APIMatchers::KeyNotFound 21 | false # the key was not found 22 | end 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /lib/api_matchers/response_body/have_node.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | module ResponseBody 3 | class HaveNode 4 | end 5 | end 6 | end -------------------------------------------------------------------------------- /lib/api_matchers/response_body/have_xml_node.rb: -------------------------------------------------------------------------------- 1 | require 'nokogiri' 2 | 3 | module APIMatchers 4 | module ResponseBody 5 | class HaveXmlNode < Base 6 | def matches?(actual) 7 | value = false 8 | @actual = actual 9 | xml = Nokogiri::XML(response_body) 10 | 11 | node_set = xml.xpath("//#{@expected_node}") 12 | if node_set 13 | node_set.each do |node| 14 | if @with_value 15 | value = (node.text == @with_value.to_s) 16 | elsif @expected_including_text 17 | value = (node.text.to_s.include?(@expected_including_text)) 18 | else 19 | value = node.text.present? 20 | end 21 | # if value is true, time to return 22 | return value if value 23 | end 24 | end 25 | # at this point, it failed to match 26 | return value 27 | end 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /lib/api_matchers/version.rb: -------------------------------------------------------------------------------- 1 | module APIMatchers 2 | VERSION = '0.6.2' 3 | end 4 | -------------------------------------------------------------------------------- /spec/api_matchers/core/find_in_json_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module APIMatchers::Core 4 | RSpec.describe FindInJSON do 5 | describe "#find" do 6 | context 'when node exists' do 7 | it "should return the value of the expected key" do 8 | expect(FindInJSON.new('product' => 'gateway').find(node: 'product')).to eql 'gateway' 9 | end 10 | 11 | it "should return the value of the deep expected key in the json" do 12 | expect(FindInJSON.new('transaction' => { 'error' => { 'code' => '999' } }).find(node: 'code')).to eql '999' 13 | end 14 | end 15 | 16 | context 'when node do not exists' do 17 | it "should return nil if don't find the expected node" do 18 | expect { FindInJSON.new('product' => 'pabx').find(node: 'developers') }.to raise_error( ::APIMatchers::Core::Exceptions::KeyNotFound) 19 | end 20 | 21 | it "should return nil if don't find the expected node in the deep JSON" do 22 | expect { FindInJSON.new('transaction' => { 'id' => 150, 'error' => {} }).find(node: 'code') }.to raise_error( ::APIMatchers::Core::Exceptions::KeyNotFound) 23 | end 24 | end 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /spec/api_matchers/core/setup_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::Core::Setup do 4 | end -------------------------------------------------------------------------------- /spec/api_matchers/headers/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::Headers::Base do 4 | let(:setup) { OpenStruct.new } 5 | subject { APIMatchers::Headers::Base.new(setup) } 6 | 7 | describe "#matches?" do 8 | it "should raise Not Implement Exception" do 9 | expect { subject.matches?('application/xml') }.to raise_error(NotImplementedError, "not implemented on #{subject}") 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /spec/api_matchers/headers/be_json_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::Headers::BeJSON do 4 | describe "actual.to be_json" do 5 | it "should pass when the actual is json response" do 6 | expect("application/json; charset=utf-8").to be_json 7 | end 8 | 9 | it "should not pass when the actual is not a json response" do 10 | expect { 11 | expect("application/xml; charset=utf-8").to be_json 12 | }.to fail_with(%Q{expected a JSON response with 'application/json; charset=utf-8'. Got: 'application/xml; charset=utf-8'.}) 13 | end 14 | end 15 | 16 | describe "actual.not_to be_json" do 17 | it "should pass when the actual is not a json response" do 18 | expect("application/xml; charset=utf-8").not_to be_json 19 | end 20 | 21 | it "should not pass when the actual is a json response" do 22 | expect{ 23 | expect("application/json; charset=utf-8").not_to be_json 24 | }.to fail_with(%Q{expected to not be a JSON response. Got: 'application/json; charset=utf-8'.}) 25 | end 26 | end 27 | 28 | describe "with change configuration" do 29 | before do 30 | APIMatchers.setup do |config| 31 | config.header_method = :response_header 32 | config.header_content_type_key = 'Content-Type' 33 | end 34 | end 35 | 36 | after do 37 | APIMatchers.setup do |config| 38 | config.header_method = nil 39 | config.header_content_type_key = nil 40 | end 41 | end 42 | 43 | it "should pass if the actual.response_header is equal to application/json" do 44 | response = OpenStruct.new(:response_header => { 'Content-Type' => "application/json; charset=utf-8"}) 45 | expect(response).to be_json 46 | end 47 | 48 | it "should fail if the actual.response_header is not equal to application/json" do 49 | response = OpenStruct.new(:response_header => { "Content-Type" => "application/xml; charset=utf-8"}) 50 | expect { 51 | expect(response).to be_json 52 | }.to fail_with(%Q{expected a JSON response with 'application/json; charset=utf-8'. Got: 'application/xml; charset=utf-8'.}) 53 | end 54 | 55 | it "should fail if the actual.response_header is not equal to application/json" do 56 | response = OpenStruct.new(:response_header => { "foo-bar" => "application/xml; charset=utf-8"}) 57 | expect { 58 | expect(response).to be_json 59 | }.to fail_with(%Q{expected a JSON response with 'application/json; charset=utf-8'. Got: '{"foo-bar"=>"application/xml; charset=utf-8"}'.}) 60 | end 61 | end 62 | end -------------------------------------------------------------------------------- /spec/api_matchers/headers/be_xml_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::Headers::BeXML do 4 | describe "actual.to be_xml" do 5 | it "should pass when the actual is json response" do 6 | expect("application/xml; charset=utf-8").to be_xml 7 | end 8 | 9 | it "should not pass when the actual is not a json response" do 10 | expect { 11 | expect("application/json; charset=utf-8").to be_xml 12 | }.to fail_with(%Q{expected a XML response with 'application/xml; charset=utf-8'. Got: 'application/json; charset=utf-8'.}) 13 | end 14 | end 15 | 16 | describe "actual.not_to be_xml" do 17 | it "should pass when the actual is not a json response" do 18 | expect("application/json; charset=utf-8").not_to be_xml 19 | end 20 | 21 | it "should not pass when the actual is a json response" do 22 | expect { 23 | expect("application/xml; charset=utf-8").not_to be_xml 24 | }.to fail_with(%Q{expected to not be a XML response. Got: 'application/xml; charset=utf-8'.}) 25 | end 26 | end 27 | 28 | describe "with change configuration" do 29 | before do 30 | APIMatchers.setup do |config| 31 | config.header_method = :response_header 32 | config.header_content_type_key = 'Content-Type' 33 | end 34 | end 35 | 36 | after do 37 | APIMatchers.setup do |config| 38 | config.header_method = nil 39 | config.header_content_type_key = nil 40 | end 41 | end 42 | 43 | it "should pass if the actual.response_header is equal to application/xml" do 44 | response = OpenStruct.new(:response_header => { 'Content-Type' => "application/xml; charset=utf-8"}) 45 | expect(response).to be_xml 46 | end 47 | 48 | it "should fail if the actual.response_header is not equal to application/xml" do 49 | response = OpenStruct.new(:response_header => { "Content-Type" => "application/json; charset=utf-8"}) 50 | expect { 51 | expect(response).to be_xml 52 | }.to fail_with(%Q{expected a XML response with 'application/xml; charset=utf-8'. Got: 'application/json; charset=utf-8'.}) 53 | end 54 | 55 | it "should fail when pass the actual that have headers but not the content type key" do 56 | response = OpenStruct.new(:response_header => { "foo-baz" => "application/json; charset=utf-8"}) 57 | expect { 58 | expect(response).to be_xml 59 | }.to fail_with(%Q{expected a XML response with 'application/xml; charset=utf-8'. Got: '{"foo-baz"=>"application/json; charset=utf-8"}'.}) 60 | end 61 | end 62 | end -------------------------------------------------------------------------------- /spec/api_matchers/http_status_code/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::HTTPStatusCode::Base do 4 | let(:setup) { OpenStruct.new } 5 | subject { APIMatchers::HTTPStatusCode::Base.new(setup) } 6 | 7 | describe "#matches?" do 8 | it "should raise Not Implement Exception" do 9 | expect { subject.matches?(302) }.to raise_error(NotImplementedError, "not implemented on #{subject}") 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /spec/api_matchers/http_status_code/be_bad_request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::HTTPStatusCode::BeBadRequest do 4 | describe "should be_bad_request" do 5 | it "should passes if the actual is equal to 400" do 6 | expect(400).to be_bad_request 7 | end 8 | 9 | it "should fails if the actual is not equal to 400" do 10 | expect { 11 | expect(401).to be_bad_request 12 | }.to fail_with(%Q{expected that '401' to be a Bad Request with the status '400'.}) 13 | end 14 | end 15 | 16 | describe "should_not be_bad_request" do 17 | it "should passes if the actual is not equal to 400" do 18 | expect(401).not_to be_bad_request 19 | end 20 | 21 | it "should fail if the actual is equal to 400" do 22 | expect { 23 | expect(400).not_to be_bad_request 24 | }.to fail_with(%Q{expected that '400' to NOT be a Bad Request with the status '400'.}) 25 | end 26 | end 27 | 28 | describe "with change configuration" do 29 | before do 30 | APIMatchers.setup { |config| config.http_status_method = :http_status } 31 | end 32 | 33 | after do 34 | APIMatchers.setup { |config| config.http_status_method = nil } 35 | end 36 | 37 | it "should pass if the actual.http_status is equal to 400" do 38 | response = OpenStruct.new(:http_status => 400) 39 | expect(response).to be_bad_request 40 | end 41 | 42 | it "should fail if the actual.http_status is not equal to 400" do 43 | response = OpenStruct.new(:http_status => 500) 44 | expect { 45 | expect(response).to be_bad_request 46 | }.to fail_with(%Q{expected that '500' to be a Bad Request with the status '400'.}) 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /spec/api_matchers/http_status_code/be_forbidden_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::HTTPStatusCode::BeForbidden do 4 | describe "should be_forbidden" do 5 | it "should passes if the actual is equal to 403" do 6 | expect(403).to be_forbidden 7 | end 8 | 9 | it "should fails if the actual is not equal to 403" do 10 | expect { 11 | expect(400).to be_forbidden 12 | }.to fail_with(%Q{expected that '400' to be Forbidden with the status '403'.}) 13 | end 14 | end 15 | 16 | describe "should_not be_forbidden" do 17 | it "should pass if the actual is not equal to 403" do 18 | expect(201).not_to be_forbidden 19 | end 20 | 21 | it "should fail if the actual is equal to 403" do 22 | expect { 23 | expect(403).not_to be_forbidden 24 | }.to fail_with(%Q{expected that '403' to NOT be Forbidden.}) 25 | end 26 | end 27 | 28 | describe "with change configuration" do 29 | before do 30 | APIMatchers.setup { |config| config.http_status_method = :http_status } 31 | end 32 | 33 | after do 34 | APIMatchers.setup { |config| config.http_status_method = nil } 35 | end 36 | 37 | it "should pass if the actual.http_status is equal to 403" do 38 | response = OpenStruct.new(:http_status => 403) 39 | expect(response).to be_forbidden 40 | end 41 | 42 | it "should fail if the actual.http_status is not equal to 403" do 43 | response = OpenStruct.new(:http_status => 402) 44 | expect { 45 | expect(response).to be_forbidden 46 | }.to fail_with(%Q{expected that '402' to be Forbidden with the status '403'.}) 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /spec/api_matchers/http_status_code/be_internal_server_error_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::HTTPStatusCode::BeInternalServerError do 4 | describe "should be_internal_server_error" do 5 | it "should passes if the actual is equal to 500" do 6 | expect(500).to be_internal_server_error 7 | end 8 | 9 | it "should fails if the actual is not equal to 500" do 10 | expect { 11 | expect(401).to be_internal_server_error 12 | }.to fail_with(%Q{expected that '401' to be Internal Server Error with the status '500'.}) 13 | end 14 | end 15 | 16 | describe "should_not be_internal_server_error" do 17 | it "should passes if the actual is not equal to 500" do 18 | expect(400).not_to be_internal_server_error 19 | end 20 | 21 | it "should fail if the actual is equal to 500" do 22 | expect { 23 | expect(500).not_to be_internal_server_error 24 | }.to fail_with(%Q{expected that '500' to NOT be Internal Server Error.}) 25 | end 26 | end 27 | 28 | describe "with change configuration" do 29 | before do 30 | APIMatchers.setup { |config| config.http_status_method = :http_status } 31 | end 32 | 33 | after do 34 | APIMatchers.setup { |config| config.http_status_method = nil } 35 | end 36 | 37 | it "should pass if the actual.http_status is equal to 500" do 38 | response = OpenStruct.new(:http_status => 500) 39 | expect(response).to be_internal_server_error 40 | end 41 | 42 | it "should fail if the actual.http_status is not equal to 500" do 43 | response = OpenStruct.new(:http_status => 402) 44 | expect { 45 | expect(response).to be_internal_server_error 46 | }.to fail_with(%Q{expected that '402' to be Internal Server Error with the status '500'.}) 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /spec/api_matchers/http_status_code/be_not_found_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::HTTPStatusCode::BeBadRequest do 4 | describe "should be_not_found" do 5 | it "should passes if the actual is equal to 404" do 6 | expect(404).to be_not_found 7 | end 8 | 9 | it "should fails if the actual is not equal to 404" do 10 | expect { 11 | expect(401).to be_not_found 12 | }.to fail_with(%Q{expected that '401' to be Not Found with the status '404'.}) 13 | end 14 | end 15 | 16 | describe "should_not be_not_found" do 17 | it "should pass if the actual is not equal to 404" do 18 | expect(401).not_to be_not_found 19 | end 20 | 21 | it "should fail if the actual is equal to 404" do 22 | expect { 23 | expect(404).not_to be_not_found 24 | }.to fail_with(%Q{expected that '404' to NOT be Not Found with the status '404'.}) 25 | end 26 | end 27 | 28 | describe "with change configuration" do 29 | before do 30 | APIMatchers.setup { |config| config.http_status_method = :http_status } 31 | end 32 | 33 | after do 34 | APIMatchers.setup { |config| config.http_status_method = nil } 35 | end 36 | 37 | it "should pass if the actual.http_status is equal to 404" do 38 | response = OpenStruct.new(:http_status => 404) 39 | expect(response).to be_not_found 40 | end 41 | 42 | it "should fail if the actual.http_status is not equal to 400" do 43 | response = OpenStruct.new(:http_status => 500) 44 | expect { 45 | expect(response).to be_not_found 46 | }.to fail_with(%Q{expected that '500' to be Not Found with the status '404'.}) 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /spec/api_matchers/http_status_code/be_ok_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::HTTPStatusCode::BeOk do 4 | describe "should be_ok" do 5 | it "should passes if the actual is equal to 200" do 6 | expect(200).to be_ok 7 | end 8 | 9 | it "should fails if the actual is not equal to 200" do 10 | expect { 11 | expect(201).to be_ok 12 | }.to fail_with(%Q{expected that '201' to be ok with the status '200'.}) 13 | end 14 | end 15 | 16 | describe "should_not be_ok_request" do 17 | it "should passes if the actual is not equal to 200" do 18 | expect(201).not_to be_ok 19 | end 20 | 21 | it "should fail if the actual is equal to 200" do 22 | expect { 23 | expect(200).not_to be_ok 24 | }.to fail_with(%Q{expected that '200' to NOT be ok with the status '200'.}) 25 | end 26 | end 27 | 28 | describe "with change configuration" do 29 | before do 30 | APIMatchers.setup { |config| config.http_status_method = :http_status } 31 | end 32 | 33 | after do 34 | APIMatchers.setup { |config| config.http_status_method = nil } 35 | end 36 | 37 | it "should pass if the actual.http_status is equal to 200" do 38 | response = OpenStruct.new(:http_status => 200) 39 | expect(response).to be_ok 40 | end 41 | 42 | it "should fail if the actual.http_status is not equal to 200" do 43 | response = OpenStruct.new(:http_status => 500) 44 | expect { 45 | expect(response).to be_ok 46 | }.to fail_with(%Q{expected that '500' to be ok with the status '200'.}) 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /spec/api_matchers/http_status_code/be_unauthorized_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::HTTPStatusCode::BeUnauthorized do 4 | describe "should be_unauthorized" do 5 | it "should passes if the actual is equal to 401" do 6 | expect(401).to be_unauthorized 7 | end 8 | 9 | it "should fails if the actual is not equal to 401" do 10 | expect { 11 | expect(400).to be_unauthorized 12 | }.to fail_with(%Q{expected that '400' to be Unauthorized with the status '401'.}) 13 | end 14 | end 15 | 16 | describe "should_not be_unauthorized" do 17 | it "should pass if the actual is not equal to 401" do 18 | expect(201).not_to be_unauthorized 19 | end 20 | 21 | it "should fail if the actual is equal to 401" do 22 | expect { 23 | expect(401).not_to be_unauthorized 24 | }.to fail_with(%Q{expected that '401' to NOT be Unauthorized.}) 25 | end 26 | end 27 | 28 | describe "with change configuration" do 29 | before do 30 | APIMatchers.setup { |config| config.http_status_method = :http_status } 31 | end 32 | 33 | after do 34 | APIMatchers.setup { |config| config.http_status_method = nil } 35 | end 36 | 37 | it "should pass if the actual.http_status is equal to 401" do 38 | response = OpenStruct.new(:http_status => 401) 39 | expect(response).to be_unauthorized 40 | end 41 | 42 | it "should fail if the actual.http_status is not equal to 401" do 43 | response = OpenStruct.new(:http_status => 402) 44 | expect { 45 | expect(response).to be_unauthorized 46 | }.to fail_with(%Q{expected that '402' to be Unauthorized with the status '401'.}) 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /spec/api_matchers/http_status_code/be_unprocessable_entity_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::HTTPStatusCode::BeUnprocessableEntity do 4 | describe "should be_unprocessable_entity" do 5 | it "should passes if the actual is equal to 422" do 6 | expect(422).to be_unprocessable_entity 7 | end 8 | 9 | it "should fails if the actual is not equal to 422" do 10 | expect { 11 | expect(500).to be_unprocessable_entity 12 | }.to fail_with(%Q{expected that '500' to be Unprocessable entity with the status '422'.}) 13 | end 14 | end 15 | 16 | describe "should_not be_unprocessable_entity" do 17 | it "should passes if the actual is not equal to 200" do 18 | expect(400).not_to be_unprocessable_entity 19 | end 20 | 21 | it "should fail if the actual is equal to 200" do 22 | expect { 23 | expect(422).not_to be_unprocessable_entity 24 | }.to fail_with(%Q{expected that '422' to NOT be Unprocessable entity with the status '422'.}) 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /spec/api_matchers/http_status_code/create_resource_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::HTTPStatusCode::CreateResource do 4 | describe "should create_resource" do 5 | it "should passes if the actual is equal to 201" do 6 | expect(201).to create_resource 7 | end 8 | 9 | it "should fails if the actual is not equal to 201" do 10 | expect { 11 | expect(200).to create_resource 12 | }.to fail_with(%Q{expected that '200' to be Created Resource with the status '201'.}) 13 | end 14 | end 15 | 16 | describe "should_not create_resource" do 17 | it "should passes if the actual is not equal to 201" do 18 | expect(401).not_to create_resource 19 | end 20 | 21 | it "should fail if the actual is equal equal to 201" do 22 | expect { 23 | expect(201).not_to create_resource 24 | }.to fail_with(%Q{expected that '201' to NOT be Created Resource.}) 25 | end 26 | end 27 | 28 | describe "with change configuration" do 29 | before do 30 | APIMatchers.setup { |config| config.http_status_method = :http_status } 31 | end 32 | 33 | after do 34 | APIMatchers.setup { |config| config.http_status_method = nil } 35 | end 36 | 37 | it "should pass if the actual.http_status is equal to 201" do 38 | response = OpenStruct.new(:http_status => 201) 39 | expect(response).to create_resource 40 | end 41 | 42 | it "should fail if the actual.http_status is not equal to 201" do 43 | response = OpenStruct.new(:http_status => 402) 44 | expect { 45 | expect(response).to create_resource 46 | }.to fail_with(%Q{expected that '402' to be Created Resource with the status '201'.}) 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /spec/api_matchers/response_body/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::ResponseBody::Base do 4 | let(:setup) { OpenStruct.new(:response_body_method => :body) } 5 | subject { APIMatchers::ResponseBody::Base.new(setup: setup, expected_node: :status) } 6 | 7 | describe "#matches?" do 8 | it "should raise Not Implemented Error" do 9 | expect { 10 | subject.matches?("foo") 11 | }.to raise_error(NotImplementedError, "not implemented on #{subject}") 12 | end 13 | end 14 | 15 | describe "#setup" do 16 | it "should read from the initialize" do 17 | expect(subject.setup).to equal setup 18 | end 19 | end 20 | 21 | describe "#expected_node" do 22 | it "should read from the initialize" do 23 | expect(subject.expected_node).to equal :status 24 | end 25 | end 26 | 27 | describe "#response_body" do 28 | let(:body) { { :foo => :bar}.to_json } 29 | 30 | context 'when have configuration' do 31 | it "should call the method when is config" do 32 | subject.actual = OpenStruct.new(:body => body) 33 | expect(subject.response_body).to eql body 34 | end 35 | end 36 | 37 | context 'when dont have configuration' do 38 | let(:setup) { OpenStruct.new(:response_body_method => nil) } 39 | subject { APIMatchers::ResponseBody::Base.new(setup: setup, expected_node: :status) } 40 | 41 | it "should return the actual when do not have config" do 42 | subject.actual = body 43 | expect(subject.response_body).to eql body 44 | end 45 | end 46 | end 47 | end -------------------------------------------------------------------------------- /spec/api_matchers/response_body/have_json_node_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::ResponseBody::HaveJsonNode do 4 | describe "actual).to have_json_node" do 5 | context 'expected key and value in top level' do 6 | it "pass when the expected key exist" do 7 | expect({ :product => 'gateway' }.to_json).to have_json_node(:product) 8 | end 9 | 10 | it "fail when the expected key does not exist" do 11 | expect { 12 | expect({ :product => 'pabx' }.to_json).to have_json_node(:developers) 13 | }.to fail_with(%Q{expected to have node called: 'developers'. Got: '{"product":"pabx"}'}) 14 | end 15 | 16 | it "pass when the expected key exist with the expected value" do 17 | expect({ :product => 'payment-gateway' }.to_json).to have_json_node(:product).with('payment-gateway') 18 | end 19 | 20 | it "pass when the expected key exist with the expected value (as integer)" do 21 | expect({ :number => 1 }.to_json).to have_json_node(:number).with(1) 22 | end 23 | 24 | it "pass when the expected key exist with the expected value (as boolean, true)" do 25 | expect({ :number => true }.to_json).to have_json_node(:number).with(true) 26 | end 27 | 28 | it "pass when the expected key exist with the expected value (as boolean, false)" do 29 | expect({ :number => false }.to_json).to have_json_node(:number).with(false) 30 | end 31 | 32 | it "pass when the expected key exist but the expected value is wrong (as boolean, true)" do 33 | expect({ :number => true }.to_json).not_to have_json_node(:number).with(false) 34 | end 35 | 36 | it "pass when the expected key exist but the expected value is wrong (as boolean, false)" do 37 | expect({ :number => false }.to_json).not_to have_json_node(:number).with(true) 38 | end 39 | 40 | it "pass when the expected key exists with the expected value (as DateTime)" do 41 | now = DateTime.now.to_s 42 | expect({ :date => now }.to_json).to have_json_node(:date).with(now) 43 | end 44 | 45 | it "pass when the expected key exists with the expected value (as Date)" do 46 | now = Date.today 47 | expect({ :date => now }.to_json).to have_json_node(:date).with(now) 48 | end 49 | 50 | it "pass when the expected key exists with the expected value (as Time)" do 51 | now = Time.now.to_s 52 | expect({ :time => now }.to_json).to have_json_node(:time).with(now) 53 | end 54 | 55 | it "fail when the expected key exist but the expected value don't exist" do 56 | expect { 57 | expect({ :product => 'payment-gateway' }.to_json).to have_json_node(:product).with('email-marketing') 58 | }.to fail_with(%Q{expected to have node called: 'product' with value: 'email-marketing'. Got: '{"product":"payment-gateway"}'}) 59 | end 60 | 61 | it "not parse the matcher for json when you pass a xml" do 62 | expect { 63 | expect("webdesk").to have_json_node(:name).with('webdesk') 64 | }.to raise_error(APIMatchers::InvalidJSON, "Invalid JSON: 'webdesk'") 65 | end 66 | end 67 | 68 | context 'expected key and nil value' do 69 | it "pass when the expected key exists" do 70 | expect({ :product => nil }.to_json).to have_json_node(:product) 71 | end 72 | 73 | it "pass when the expected key exists and the expected value is nil" do 74 | expect({ :product => nil }.to_json).to have_json_node(:product).with( nil ) 75 | end 76 | 77 | it "fail when the expected key exist but the expected value don't exist" do 78 | expect { 79 | expect({ :product => nil }.to_json).to have_json_node(:product).with('email-marketing') 80 | }.to fail_with(%Q{expected to have node called: 'product' with value: 'email-marketing'. Got: '{"product":null}'}) 81 | end 82 | end 83 | 84 | context 'expected key and value in more deep in the JSON' do 85 | context '.to_json used' do 86 | it "pass when the expected key exist" do 87 | expect({ :transaction => { :id => 150 } }.to_json).to have_json_node(:id) 88 | end 89 | 90 | it "pass when the expected key and expected value exist" do 91 | expect({ :transaction => { :error => { :code => '999' } } }.to_json).to have_json_node(:code).with('999') 92 | end 93 | 94 | it "pass when the expected key and expected value exist in very deep" do 95 | expect({ :first=>"A", :second=>nil, :third=>{ :stuff => { :first_stuff=>{ :color=>"green", :size=>"small", :shape=>"circle", :uid=>"first_stuff"}, :second_stuff=>{ :color=>"blue", :size=>"large", :shape=>"square", :uid=>"second_stuff"}}, :junk=>[{"name"=>"junk_one", :uid=>"junk_one", :stuff_uid=>"first_stuff"}, { :name=>"junk_two", :uid=>"junk_two", :stuff_uid=>"second_stuff"}]}}.to_json).to have_json_node( :junk ) 96 | end 97 | 98 | it "pass when the expected key and expected value exist in very deep" do 99 | expect({ :first=>"A", :second=>nil, :third=>{ :stuff => { :first_stuff=>{ :color=>"green", :size=>"small", :shape=>"circle", :uid=>"first_stuff"}, :second_stuff=>{ :color=>"blue", :size=>"large", :shape=>"square", :uid=>"second_stuff"}}, :junk=>[{"name"=>"junk_one", :uid=>"junk_one", :stuff_uid=>"first_stuff"}, { :name=>"junk_two", :uid=>"junk_two", :stuff_uid=>"second_stuff"}]}}.to_json).to have_json_node( :name ).with( "junk_two" ) 100 | end 101 | 102 | it "fail when the expected key does not exist" do 103 | expect { 104 | expect({ :transaction => { :id => 150, :error => {} } }.to_json).to have_json_node(:code) 105 | }.to fail_with(%Q{expected to have node called: 'code'. Got: '{"transaction":{"id":150,"error":{}}}'}) 106 | end 107 | 108 | it "fail when the expected key exist but don't exist the expected value" do 109 | expect { 110 | expect({ :transaction => { :id => 150, :error => { :code => '999' } } }.to_json).to have_json_node(:code).with('001') 111 | }.to fail_with(%Q{expected to have node called: 'code' with value: '001'. Got: '{"transaction":{"id":150,"error":{"code":"999"}}}'}) 112 | end 113 | end 114 | context 'json string used' do 115 | it "pass when the expected key exist" do 116 | expect('{ "transaction": {"id": 150 } }').to have_json_node(:id) 117 | end 118 | 119 | it "pass when the expected key and expected value exist" do 120 | expect('{ "transaction": {"error": { "code": "999" } } }').to have_json_node(:code).with('999') 121 | end 122 | 123 | it "pass when the expected key exist with the expected value (as integer)" do 124 | expect('{"number":1 }').to have_json_node(:number).with(1) 125 | end 126 | 127 | it "pass when the expected key exist with the expected value (as boolean)" do 128 | expect('{"boolean":true}').to have_json_node(:boolean).with(true) 129 | end 130 | 131 | it "pass when the expected key exists with the expected value (as DateTime)" do 132 | now = DateTime.parse( "2012-09-18T15:42:12-07:00" ) 133 | expect('{"date": "2012-09-18T15:42:12-07:00"}').to have_json_node(:date).with(now) 134 | end 135 | 136 | it "pass when the expected key exists with the expected value (as Date)" do 137 | now = Date.parse( "2012-09-18" ) 138 | expect('{"date": "2012-09-18"}').to have_json_node(:date).with(now) 139 | end 140 | 141 | it "pass when the expected key exists with the expected value (as Time)" do 142 | now = Time.parse("2012-09-18T15:42:12Z") 143 | expect('{"time": "2012-09-18T15:42:12+00:00"}').to have_json_node(:time).with(now) 144 | end 145 | 146 | it "pass when the expected key exist with the expected value (as boolean) in a multi node" do 147 | expect('{"uid":"123456","boolean":true}').to have_json_node(:boolean).with(true) 148 | end 149 | 150 | it "pass when the expected key and expected value exist in very deep" do 151 | expect('{"first":"A","second":null,"third":{"stuff":{"first_stuff":{"color":"green","size":"small","shape":"circle","uid":"first_stuff"},"second_stuff":{"color":"blue","size":"large","shape":"square","uid":"second_stuff"}},"junk":[{"name":"junk_one","uid":"junk_one","stuff_uid":"first_stuff"},{"name":"junk_two","uid":"junk_two","stuff_uid":"second_stuff"}]}}').to have_json_node( :junk ) 152 | end 153 | 154 | it "pass when the expected key and expected value exist in very deep" do 155 | expect('{"first":"A","second":null,"third":{"stuff":{"first_stuff":{"color":"green","size":"small","shape":"circle","uid":"first_stuff"},"second_stuff":{"color":"blue","size":"large","shape":"square","uid":"second_stuff"}},"junk":[{"name":"junk_one","uid":"junk_one","stuff_uid":"first_stuff"},{"name":"junk_two","uid":"junk_two","stuff_uid":"second_stuff"}]}}').to have_json_node( :name ).with( "junk_two" ) 156 | end 157 | 158 | it "pass when the expected key and including text exist" do 159 | expect('{"Key":"A=123456-abcdef-09876-ghijkl; path=/; expires=Sun, 05-Sep-2032 05:50:39 GMT\nB=ABCDEF123456; path=/; expires=Sun, 05-Sep-2032 05:50:39 GMT", "Content-Type":"application/json; charset=utf-8"}').to have_json_node( "Key" ).including_text( "123456-abcdef-09876-ghijkl" ) 160 | end 161 | 162 | it "fail when the expected key does not exist" do 163 | expect { 164 | expect('{"transaction":{"id":150,"error":{}}}').to have_json_node(:code) 165 | }.to fail_with(%Q{expected to have node called: 'code'. Got: '{"transaction":{"id":150,"error":{}}}'}) 166 | end 167 | 168 | it "fail when the expected key exist but don't exist the expected value" do 169 | expect { 170 | expect('{"transaction":{"id":150,"error":{"code":"999"}}}').to have_json_node(:code).with('001') 171 | }.to fail_with(%Q{expected to have node called: 'code' with value: '001'. Got: '{"transaction":{"id":150,"error":{"code":"999"}}}'}) 172 | end 173 | end 174 | end 175 | 176 | context "including_text" do 177 | let(:json) do 178 | { :transaction => { :error => { :message => "Transaction error: Name can't be blank" } } }.to_json 179 | end 180 | 181 | it "pass when the expected is included in the actual" do 182 | expect({ :transaction => { :error => { :message => "Transaction error: Name can't be blank" } } }.to_json).to have_json_node(:message).including_text("Transaction error") 183 | end 184 | 185 | it "fail when the expected is not included in the actual" do 186 | expect { 187 | expect(json).to have_json_node(:message).including_text("Fox on the run") 188 | }.to fail_with(%Q{expected to have node called: 'message' including text: 'Fox on the run'. Got: '{"transaction":{"error":{"message":"Transaction error: Name can't be blank"}}}'}) 189 | end 190 | end 191 | end 192 | 193 | describe "actual).not_to have_json_node" do 194 | it "pass when don't have the expected node in root level" do 195 | expect({ :product => 'gateway' }.to_json).not_to have_json_node(:status) 196 | end 197 | 198 | it "pass when don't have the expected node in any level" do 199 | expect({ :transaction => { :id => 12, :status => 'paid' } }.to_json).not_to have_json_node(:error) 200 | end 201 | 202 | it "fail when the expected key exist" do 203 | expect { 204 | expect({ :status => 'paid' }.to_json).not_to have_json_node(:status) 205 | }.to fail_with(%Q{expected to NOT have node called: 'status'. Got: '{"status":"paid"}'}) 206 | end 207 | 208 | it "pass when have the expected key but have a different value" do 209 | expect({ :status => 'paid' }.to_json).not_to have_json_node(:status).with('not_authorized') 210 | end 211 | 212 | it "fail when have the expected key and have the expected value" do 213 | expect { 214 | expect({ :status => 'paid' }.to_json).not_to have_json_node(:status).with('paid') 215 | }.to fail_with(%Q{expected to NOT have node called: 'status' with value: 'paid'. Got: '{"status":"paid"}'}) 216 | end 217 | 218 | context "including_text" do 219 | it "pass when the expected is NOT included in the actual" do 220 | expect({ :transaction => { :error => { :message => "Transaction error: Name can't be blank" } } }.to_json).not_to have_json_node(:message).including_text("Love gun") 221 | end 222 | 223 | it "fail when the expected is included in the actual" do 224 | expect { 225 | expect({ :transaction => { :error => { :message => "Transaction error: Name can't be blank" } } }.to_json).not_to have_json_node(:message).including_text("Transaction error") 226 | }.to fail_with(%Q{expected to NOT have node called: 'message' including text: 'Transaction error'. Got: '{"transaction":{"error":{"message":"Transaction error: Name can't be blank"}}}'}) 227 | end 228 | end 229 | end 230 | 231 | describe "some assumptions" do 232 | it "pass when have the json node name" do 233 | expect('{ "transaction": { "id": 54, "status": "paid" } }').to have_json_node(:transaction) 234 | end 235 | 236 | it "pass when have json node with integer value" do 237 | expect('{ "transaction": { "id": 54, "status": "paid" } }').to have_json_node(:id).with(54) 238 | end 239 | 240 | it "should have json node including text" do 241 | expect('{"error": "Transaction error: Name cant be blank"}').to have_json_node(:error).including_text("Transaction error") 242 | end 243 | 244 | it "pass have json node with boolean value" do 245 | expect('{"creditcard": true}').to have_json_node(:creditcard).with(true) 246 | end 247 | 248 | it "pass have json node with string" do 249 | expect('{ "error": "not_authorized", "transaction": { "id": "55" } }').to have_node(:error).with('not_authorized') 250 | end 251 | 252 | it "pass have json node with integer" do 253 | expect('{"parcels": 1 }').to have_node(:parcels).with(1) 254 | end 255 | end 256 | 257 | describe "with change configuration" do 258 | before do 259 | APIMatchers.setup { |config| config.response_body_method = :response_body } 260 | end 261 | 262 | after do 263 | APIMatchers.setup { |config| config.response_body_method = nil } 264 | end 265 | 266 | it "pass if the actual.http_status is equal to 400" do 267 | response = OpenStruct.new(:response_body => { :foo => :bar }.to_json) 268 | expect(response).to have_json_node(:foo).with('bar') 269 | end 270 | 271 | it "fail if the actual.http_status is not equal to 400" do 272 | response = OpenStruct.new(:response_body => { :baz => :foo}.to_json) 273 | expect { 274 | expect(response).to have_json_node(:bar) 275 | }.to fail_with(%Q{expected to have node called: 'bar'. Got: '{"baz":"foo"}'}) 276 | end 277 | end 278 | end 279 | -------------------------------------------------------------------------------- /spec/api_matchers/response_body/have_json_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::ResponseBody::HaveJson do 4 | describe "actual).to have_json" do 5 | context 'when pass' do 6 | it 'equal json' do 7 | expect(['Petshop', 'Dogs'].to_json).to have_json(['Petshop', 'Dogs']) 8 | end 9 | 10 | it 'compares with Hash too' do 11 | expect({ 'offers' => [10, 90] }.to_json).to have_json({ 'offers' => [10,90] }) 12 | end 13 | end 14 | 15 | context 'when fails' do 16 | it 'fails with the expected response nd the actual body in the fail message' do 17 | expect { 18 | expect(['Petshop', 'Cats'].to_json).to have_json(['Petshop', 'Dogs']) 19 | }.to fail_with(%Q{expect to have json: '["Petshop", "Dogs"]'. Got: '["Petshop", "Cats"]'.}) 20 | end 21 | end 22 | end 23 | 24 | describe "actual).not_to have_json" do 25 | context 'when pass' do 26 | it 'different json' do 27 | expect(['Petshop', 'Cats'].to_json).not_to have_json(['Petshop', 'Dogs']) 28 | end 29 | end 30 | 31 | context 'when fails' do 32 | it 'equal json returns the fail message for expect not to' do 33 | expect { 34 | expect(['Petshop', 'Cats'].to_json).not_to have_json(['Petshop', 'Cats']) 35 | }.to fail_with(%Q{expect to NOT have json: '["Petshop","Cats"]'.}) 36 | end 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /spec/api_matchers/response_body/have_node_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::ResponseBody::HaveNode do 4 | describe "in json" do 5 | before do 6 | APIMatchers.setup { |config| config.have_node_matcher = :json } 7 | end 8 | 9 | it "should parse the matcher for json" do 10 | expect({ :product => 'chat' }.to_json).to have_node(:product).with('chat') 11 | end 12 | end 13 | 14 | describe "in xml" do 15 | before do 16 | APIMatchers.setup { |config| config.have_node_matcher = :xml } 17 | end 18 | 19 | it "should parse the matcher for xml" do 20 | expect("chat").to have_node(:product).with('chat') 21 | end 22 | 23 | after do 24 | APIMatchers.setup { |config| config.have_node_matcher = :json } 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /spec/api_matchers/response_body/have_xml_node_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe APIMatchers::ResponseBody::HaveXmlNode do 4 | describe "actual).to have_xml_node" do 5 | context 'expected key and value in top level' do 6 | it "should pass when the expected key exist" do 7 | expect("gateway").to have_xml_node(:product) 8 | end 9 | 10 | it "should fail when the expected key does not exist" do 11 | expect { 12 | expect("pabx").to have_xml_node(:developers) 13 | }.to fail_with(%Q{expected to have node called: 'developers'. Got: 'pabx'}) 14 | end 15 | 16 | it "should pass when the expected key exist with the expected value" do 17 | expect("payment-gateway").to have_xml_node(:product).with('payment-gateway') 18 | end 19 | 20 | it "should fail when the expected key exist but the expected value don't exist" do 21 | expect { 22 | expect("payment-gateway").to have_xml_node(:product).with('email-marketing') 23 | }.to fail_with(%Q{expected to have node called: 'product' with value: 'email-marketing'. Got: 'payment-gateway'}) 24 | end 25 | 26 | it "should not parse the matcher for xml when you pass a json" do 27 | expect { 28 | expect({ :name => 'webdesk'}.to_json).to have_xml_node(:name).with('webdesk') 29 | }.to fail_with(%Q{expected to have node called: 'name' with value: 'webdesk'. Got: '{"name":"webdesk"}'}) 30 | end 31 | end 32 | 33 | context 'expected key and value in more deep in the XML' do 34 | it "should pass when the expected key exist" do 35 | expect("150").to have_xml_node(:id) 36 | end 37 | 38 | it "should pass when the expected key and expected value exist" do 39 | expect("999").to have_xml_node(:code).with('999') 40 | end 41 | 42 | it "should fail when the expected key does not exist" do 43 | expect { 44 | expect("").to have_xml_node(:code) 45 | }.to fail_with(%Q{expected to have node called: 'code'. Got: ''}) 46 | end 47 | 48 | it "should fail when the expected key exist but don't exist the expected value" do 49 | expect { 50 | expect("999").to have_xml_node(:code).with('001') 51 | }.to fail_with(%Q{expected to have node called: 'code' with value: '001'. Got: '999'}) 52 | end 53 | end 54 | 55 | context "including_text" do 56 | it "should pass when the expected is included in the actual" do 57 | expect("Transaction error: Name can't be blank").to have_xml_node(:message).including_text("Transaction error") 58 | end 59 | 60 | it "should fail when the expected is not included in the actual" do 61 | expect { 62 | expect("Transaction error: Name can't be blank").to have_xml_node(:message).including_text("Fox on the run") 63 | }.to fail_with(%Q{expected to have node called: 'message' including text: 'Fox on the run'. Got: 'Transaction error: Name can't be blank'}) 64 | end 65 | end 66 | 67 | context "find matching node when multiple records" do 68 | it "should pass when the expected is included in the actual (1 level)" do 69 | expect(%{ 70 | 71 | 4 72 | 2 73 | 74 | }).to have_xml_node(:id).with(2) 75 | end 76 | 77 | it "should fail when the expected is not included in the actual (1 level)" do 78 | expect(%{ 79 | 80 | 4 81 | 2 82 | 83 | }).not_to have_xml_node(:id).with(3) 84 | end 85 | 86 | it "should pass when the expected is included in the actual (2 levels)" do 87 | expect(%{ 88 | 89 |
4
90 |
2
91 |
92 | }).to have_xml_node(:id).with(2) 93 | end 94 | 95 | it "should fail when the expected is not included in the actual (2 levels)" do 96 | expect(%{ 97 | 98 |
4
99 |
2
100 |
101 | }).not_to have_xml_node(:id).with(3) 102 | end 103 | end 104 | end 105 | 106 | describe "actual).not_to have_xml_node" do 107 | it "should pass when don't have the expected node in root level" do 108 | expect("gateway").not_to have_xml_node(:status) 109 | end 110 | 111 | it "should pass when don't have the expected node in any level" do 112 | expect("12paid").not_to have_xml_node(:error) 113 | end 114 | 115 | it "should fail when the expected key exist" do 116 | expect { 117 | expect("paid").not_to have_xml_node(:status) 118 | }.to fail_with(%Q{expected to NOT have node called: 'status'. Got: 'paid'}) 119 | end 120 | 121 | it "should pass when have the expected key but have a different value" do 122 | expect("paid").not_to have_xml_node(:status).with('not_authorized') 123 | end 124 | 125 | it "should fail when have the expected key and have the expected value" do 126 | expect { 127 | expect("paid").not_to have_xml_node(:status).with('paid') 128 | }.to fail_with(%Q{expected to NOT have node called: 'status' with value: 'paid'. Got: 'paid'}) 129 | end 130 | 131 | context "including_text" do 132 | it "should pass when the expected is included in the actual" do 133 | expect("Transaction error: Name can't be blank").not_to have_xml_node(:message).including_text("Girls of Summer") 134 | end 135 | 136 | it "should fail when the expected is not included in the actual" do 137 | expect { 138 | expect("Transaction error: Name can't be blank").not_to have_xml_node(:message).including_text("Transaction error") 139 | }.to fail_with(%Q{expected to NOT have node called: 'message' including text: 'Transaction error'. Got: 'Transaction error: Name can't be blank'}) 140 | end 141 | end 142 | end 143 | 144 | 145 | describe "with change configuration" do 146 | before do 147 | APIMatchers.setup { |config| config.response_body_method = :response_body } 148 | end 149 | 150 | after do 151 | APIMatchers.setup { |config| config.response_body_method = nil } 152 | end 153 | 154 | it "should pass if the actual.http_status is equal to 400" do 155 | response = OpenStruct.new(:response_body => "bar") 156 | expect(response).to have_xml_node(:foo).with('bar') 157 | end 158 | 159 | it "should fail if the actual.http_status is not equal to 400" do 160 | response = OpenStruct.new(:response_body => "bar") 161 | expect { 162 | expect(response).to have_xml_node(:bar) 163 | }.to fail_with(%Q{expected to have node called: 'bar'. Got: 'bar'}) 164 | end 165 | end 166 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'active_support' 2 | require 'api_matchers' 3 | require 'ostruct' 4 | 5 | RSpec.configure do |config| 6 | config.disable_monkey_patching! 7 | config.include APIMatchers::RSpecMatchers 8 | 9 | def fail_with(message) 10 | raise_error(RSpec::Expectations::ExpectationNotMetError, message) 11 | end 12 | end 13 | --------------------------------------------------------------------------------