rspec-api-matchers 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +24 -19
- data/lib/rspec-api/matchers.rb +38 -21
- data/lib/rspec-api/matchers/attributes/have_attributes.rb +23 -0
- data/lib/rspec-api/matchers/attributes/matcher.rb +101 -0
- data/lib/rspec-api/matchers/collection/be_a_collection.rb +20 -0
- data/lib/rspec-api/matchers/collection/matcher.rb +19 -0
- data/lib/rspec-api/matchers/content_type/have_content_type.rb +24 -0
- data/lib/rspec-api/matchers/content_type/matcher.rb +23 -0
- data/lib/rspec-api/matchers/filter/be_filtered.rb +20 -0
- data/lib/rspec-api/matchers/filter/matcher.rb +57 -0
- data/lib/rspec-api/matchers/headers/have_headers.rb +20 -0
- data/lib/rspec-api/matchers/headers/matcher.rb +28 -0
- data/lib/rspec-api/matchers/json/be_valid_json.rb +20 -0
- data/lib/rspec-api/matchers/json/matcher.rb +41 -0
- data/lib/rspec-api/matchers/jsonp/be_wrapped_in_callback.rb +24 -0
- data/lib/rspec-api/matchers/jsonp/matcher.rb +23 -0
- data/lib/rspec-api/matchers/page_links/have_page_links.rb +25 -0
- data/lib/rspec-api/matchers/page_links/matcher.rb +18 -0
- data/lib/rspec-api/matchers/response/be_a_valid_response.rb +20 -0
- data/lib/rspec-api/matchers/response/matcher.rb +52 -0
- data/lib/rspec-api/matchers/sort/be_sorted.rb +20 -0
- data/lib/rspec-api/matchers/sort/matcher.rb +56 -0
- data/lib/rspec-api/matchers/status/have_status.rb +25 -0
- data/lib/rspec-api/matchers/status/matcher.rb +46 -0
- data/lib/rspec-api/matchers/version.rb +1 -1
- metadata +24 -18
- data/lib/rspec-api/dsl/be_a_collection.rb +0 -24
- data/lib/rspec-api/dsl/be_a_jsonp.rb +0 -24
- data/lib/rspec-api/dsl/be_filtered.rb +0 -24
- data/lib/rspec-api/dsl/be_sorted.rb +0 -24
- data/lib/rspec-api/dsl/have_attributes.rb +0 -37
- data/lib/rspec-api/dsl/have_prev_page_link.rb +0 -20
- data/lib/rspec-api/dsl/have_status.rb +0 -20
- data/lib/rspec-api/dsl/include_content_type.rb +0 -24
- data/lib/rspec-api/matchers/attributes.rb +0 -171
- data/lib/rspec-api/matchers/collection.rb +0 -42
- data/lib/rspec-api/matchers/content_type.rb +0 -26
- data/lib/rspec-api/matchers/filter.rb +0 -58
- data/lib/rspec-api/matchers/jsonp.rb +0 -27
- data/lib/rspec-api/matchers/prev_page_link.rb +0 -23
- data/lib/rspec-api/matchers/sort.rb +0 -65
- data/lib/rspec-api/matchers/status.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a9cb40b04968465419bdc9816a1002fe6a29761
|
4
|
+
data.tar.gz: f1ca85d85451586be343f9edf7bb2d6405590578
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5315d2e00d92d287059754dbef2aa689305f43ed5e66b5a580b332aed9430a1cc4741b9a1f96b768f5c074e45910c1d31a4b9c6ad04301461407d6f1bdfcbbd
|
7
|
+
data.tar.gz: 59b6add713ca2f7988ffca56bddbb5fb64efc069066f01e283ac135ed00382fcf3834f573c7dba9d7920c59fad110ce850c903e4ccdc54738eb267ae429dae89
|
data/README.md
CHANGED
@@ -1,42 +1,47 @@
|
|
1
|
-
|
1
|
+
RSpecApi::Matchers
|
2
2
|
==================
|
3
3
|
|
4
4
|
RSpecApi::Matchers lets you express outcomes on the response of web APIs.
|
5
5
|
|
6
6
|
expect(response).to have_status(:not_found)
|
7
7
|
|
8
|
-
More documentation and examples about
|
8
|
+
More documentation and examples about RSpecApi are available at [http://rspec-api.github.io](http://rspec-api.github.io)
|
9
9
|
|
10
10
|
[](https://travis-ci.org/rspec-api/rspec-api-matchers)
|
11
11
|
[](https://codeclimate.com/github/rspec-api/rspec-api-matchers)
|
12
12
|
[](https://coveralls.io/r/rspec-api/rspec-api-matchers)
|
13
13
|
[](https://gemnasium.com/rspec-api/rspec-api-matchers)
|
14
14
|
|
15
|
-
Install
|
16
|
-
-------
|
17
|
-
|
18
|
-
Add `gem 'rspec-api-matchers'` to your `Gemfile` and run `bundle`.
|
19
|
-
Or install yourself by running `gem install rspec-api-matchers`.
|
20
|
-
|
21
|
-
Version changes respect [Semantic Versioning](http://semver.org).
|
22
|
-
To ensure `bundle update` won't cause problems in your repository,
|
23
|
-
always indicate the patch version until version 1.0.0 is released, e.g.:
|
24
|
-
|
25
|
-
gem 'rspec-api-matchers', '~> 0.5.0'
|
26
|
-
|
27
|
-
|
28
15
|
Available matchers
|
29
16
|
------------------
|
30
17
|
|
31
18
|
expect(response).to have_status(:ok)
|
32
|
-
expect(response).to
|
33
|
-
expect(response).to
|
19
|
+
expect(response).to have_content_type(:json)
|
20
|
+
expect(response).to have_page_links
|
34
21
|
expect(response).to be_a_collection
|
35
|
-
expect(response).to
|
22
|
+
expect(response).to be_wrapped_in_callback('alert')
|
36
23
|
expect(response).to be_sorted(by: :id, verse: :desc)
|
37
|
-
expect(response).to be_filtered(
|
24
|
+
expect(response).to be_filtered(by: :id, value: 10)
|
38
25
|
expect(response).to have_attributes(id: {value: 1.2}, url: {type: {string: :url}})
|
39
26
|
|
27
|
+
|
28
|
+
How to install
|
29
|
+
==============
|
30
|
+
|
31
|
+
To install on your system, run `gem install rspec-api-matchers`.
|
32
|
+
To use inside a bundled Ruby project, add this line to the Gemfile:
|
33
|
+
|
34
|
+
gem 'rspec-api-matchers', '~> 0.5.0'
|
35
|
+
|
36
|
+
The rspec-api-matchers gem follows [Semantic Versioning](http://semver.org).
|
37
|
+
Any new release that is fully backward-compatible bumps the *patch* version (0.5.1).
|
38
|
+
Any new version that breaks compatibility bumps the *minor* version (0.6.0)
|
39
|
+
|
40
|
+
Indicating the full version in your Gemfile (*major*.*minor*.*patch*) guarantees
|
41
|
+
that your project won’t occur in any error when you `bundle update` and a new
|
42
|
+
version of RSpecApi::Matchers is released.
|
43
|
+
|
44
|
+
|
40
45
|
How to contribute
|
41
46
|
=================
|
42
47
|
|
data/lib/rspec-api/matchers.rb
CHANGED
@@ -1,27 +1,44 @@
|
|
1
|
-
require 'rspec
|
2
|
-
require 'rspec-api/
|
3
|
-
require 'rspec-api/
|
4
|
-
require 'rspec-api/
|
5
|
-
require 'rspec-api/
|
6
|
-
require 'rspec-api/
|
7
|
-
require 'rspec-api/
|
8
|
-
require 'rspec-api/
|
9
|
-
|
10
|
-
require 'rspec/matchers'
|
11
|
-
|
12
|
-
# Convert RSpecAPI::Matchers classes into RSpec-compatible matchers, e.g.:
|
13
|
-
# makes RSpecApi::Matchers::Status available as expect(...).to match_status
|
1
|
+
require 'rspec/core'
|
2
|
+
require 'rspec-api/matchers/attributes/have_attributes'
|
3
|
+
require 'rspec-api/matchers/collection/be_a_collection'
|
4
|
+
require 'rspec-api/matchers/content_type/have_content_type'
|
5
|
+
require 'rspec-api/matchers/filter/be_filtered'
|
6
|
+
require 'rspec-api/matchers/json/be_valid_json'
|
7
|
+
require 'rspec-api/matchers/jsonp/be_wrapped_in_callback'
|
8
|
+
require 'rspec-api/matchers/page_links/have_page_links'
|
9
|
+
require 'rspec-api/matchers/response/be_a_valid_response'
|
10
|
+
require 'rspec-api/matchers/sort/be_sorted'
|
11
|
+
require 'rspec-api/matchers/status/have_status'
|
14
12
|
|
15
13
|
module RSpecApi
|
16
14
|
module Matchers
|
17
|
-
|
18
|
-
|
15
|
+
include Attributes
|
16
|
+
include Collection
|
17
|
+
include ContentType
|
18
|
+
include Filter
|
19
|
+
include Json
|
20
|
+
include Jsonp
|
21
|
+
include PageLinks
|
22
|
+
include Response
|
23
|
+
include Sort
|
24
|
+
include Status
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
# RSpecApi::Matchers adds matchers to test RESTful APIs.
|
29
|
+
#
|
30
|
+
# To have these matchers available inside of an RSpec `describe` block, tag that
|
31
|
+
# block with the `:rspec_api` metadata:
|
32
|
+
#
|
33
|
+
# describe "Artists", rspec_api: true do
|
34
|
+
# ... # here you can write `expect(response).to have_status :ok`, etc.
|
35
|
+
# end
|
36
|
+
RSpec.configuration.include RSpecApi::Matchers, rspec_api: true
|
37
|
+
|
38
|
+
# You can also explicitly include the RSpec::Api module inside the example group:
|
39
|
+
#
|
40
|
+
# describe "Artists" do
|
41
|
+
# include RSpecApi::Matchers
|
42
|
+
# ... # here you can write `expect(response).to have_status :ok`, etc.
|
43
|
+
# end
|
44
|
+
#
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rspec-api/matchers/attributes/matcher'
|
2
|
+
|
3
|
+
module RSpecApi
|
4
|
+
module Matchers
|
5
|
+
module Attributes
|
6
|
+
# Passes if the response body is either a JSON object or a collection of
|
7
|
+
# JSON objects that include all the +attributes+.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # Passes if the body is an object with a URL-formatted String :site
|
12
|
+
# response = OpenStruct.new body: '{"site": "http://www.example.com"}'
|
13
|
+
# expect(response).to have_attributes(site: {type: {string: :url}})
|
14
|
+
#
|
15
|
+
# For more examples check +have_attributes_spec.rb+.
|
16
|
+
|
17
|
+
# TODO: add &block options
|
18
|
+
def have_attributes(*attributes)
|
19
|
+
RSpecApi::Matchers::Attributes::Matcher.new *attributes
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'rspec-api/matchers/json/matcher'
|
2
|
+
require 'active_support/core_ext/array/wrap'
|
3
|
+
require 'active_support/core_ext/hash/keys'
|
4
|
+
|
5
|
+
module RSpecApi
|
6
|
+
module Matchers
|
7
|
+
module Attributes
|
8
|
+
class Matcher < Json::Matcher
|
9
|
+
attr_accessor :attributes
|
10
|
+
|
11
|
+
def initialize(*attributes, &block)
|
12
|
+
@attributes = to_hash attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def matches?(response)
|
16
|
+
super && has_attributes?(json, attributes)
|
17
|
+
end
|
18
|
+
|
19
|
+
def description
|
20
|
+
if attributes.any?
|
21
|
+
%Q(have attributes #{attributes})
|
22
|
+
else
|
23
|
+
%Q(have attributes)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def has_attributes?(items, attrs)
|
30
|
+
attrs.deep_symbolize_keys!
|
31
|
+
attrs.all?{|name, options| has_attribute? items, name, options}
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_attribute?(items, name, options)
|
35
|
+
return false unless Array.wrap(items).all?{|item| item.key? name}
|
36
|
+
values = Array.wrap(items).map{|item| item[name]}
|
37
|
+
attr_types = Array.wrap(options.fetch :type, :any)
|
38
|
+
attr_value = options.fetch :value, :any
|
39
|
+
values.all? do |v|
|
40
|
+
matches_value?(v, attr_value) && matches_types?(v, attr_types)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def matches_value?(value, expected_value)
|
45
|
+
case expected_value
|
46
|
+
when :any then true
|
47
|
+
when Proc then expected_value.call value
|
48
|
+
else value == expected_value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def matches_types?(value, expected_types)
|
53
|
+
expected_types.any?{|type| matches_type_and_format? value, type}
|
54
|
+
end
|
55
|
+
|
56
|
+
def matches_type_and_format?(value, type)
|
57
|
+
type = Hash[type, :any] unless type.is_a?(Hash)
|
58
|
+
type.any? do |type, format|
|
59
|
+
matches_type?(value, type) && matches_format?(value, format)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def matches_type?(value, type)
|
64
|
+
type_to_classes(type).any?{|klass| value.is_a? klass}
|
65
|
+
end
|
66
|
+
|
67
|
+
def matches_format?(value, format)
|
68
|
+
case format
|
69
|
+
when :url then value =~ URI::regexp
|
70
|
+
when :integer then value.is_a? Integer
|
71
|
+
when :timestamp then DateTime.iso8601 value rescue false
|
72
|
+
when :email then value =~ %r{(?<name>.+?)@(?<host>.+?)\.(?<domain>.+?)}
|
73
|
+
when Hash, Array then value.any? ? has_attributes?(value, to_hash(format)) : true
|
74
|
+
when String then matches_format?(value, format.to_sym)
|
75
|
+
when :any then true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def type_to_classes(type)
|
80
|
+
Array.wrap case type
|
81
|
+
when :string then String
|
82
|
+
when :array then Array
|
83
|
+
when :object then Hash
|
84
|
+
when :null then NilClass
|
85
|
+
when :boolean then [TrueClass, FalseClass]
|
86
|
+
when :number then Numeric
|
87
|
+
when String then type_to_classes(type.to_sym)
|
88
|
+
when :any then Object
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_hash(attrs)
|
93
|
+
array = Array.wrap(attrs).map do |item|
|
94
|
+
item.is_a?(Hash) ? [item.keys.first, item.values.first] : [item, {}]
|
95
|
+
end.flatten
|
96
|
+
Hash[*array]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rspec-api/matchers/collection/matcher'
|
2
|
+
|
3
|
+
module RSpecApi
|
4
|
+
module Matchers
|
5
|
+
module Collection
|
6
|
+
# Passes if the response body is a collection of JSON objects
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # Passes if the body is a JSON array
|
11
|
+
# body = '[{"id": 1}]'
|
12
|
+
# expect(OpenStruct.new body: body).to be_a_collection
|
13
|
+
#
|
14
|
+
# For more examples check +be_a_collection_spec.rb+.
|
15
|
+
def be_a_collection
|
16
|
+
RSpecApi::Matchers::Collection::Matcher.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rspec-api/matchers/json/matcher'
|
2
|
+
|
3
|
+
module RSpecApi
|
4
|
+
module Matchers
|
5
|
+
module Collection
|
6
|
+
class Matcher < Json::Matcher
|
7
|
+
|
8
|
+
def matches?(response)
|
9
|
+
super
|
10
|
+
json.is_a? Array
|
11
|
+
end
|
12
|
+
|
13
|
+
def description
|
14
|
+
%Q(be a collection)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rspec-api/matchers/content_type/matcher'
|
2
|
+
|
3
|
+
module RSpecApi
|
4
|
+
module Matchers
|
5
|
+
module ContentType
|
6
|
+
# Passes if the response headers specify the provided content +type+
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # Passes if the headers matches the provided JSON content type
|
11
|
+
# headers ={'Content-Type' => 'application/json; charset=utf-8'}
|
12
|
+
# expect(OpenStruct.new headers: headers).to have_content_type(:json)
|
13
|
+
#
|
14
|
+
# For more examples check +have_content_type_spec.rb+.
|
15
|
+
def have_content_type(type = nil)
|
16
|
+
content_type = case type
|
17
|
+
when :json then 'application/json; charset=utf-8'
|
18
|
+
when :any then %r{}
|
19
|
+
end
|
20
|
+
RSpecApi::Matchers::ContentType::Matcher.new content_type
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rspec-api/matchers/headers/matcher'
|
2
|
+
|
3
|
+
module RSpecApi
|
4
|
+
module Matchers
|
5
|
+
module ContentType
|
6
|
+
class Matcher < Headers::Matcher
|
7
|
+
attr_accessor :content_type
|
8
|
+
|
9
|
+
def initialize(content_type)
|
10
|
+
@content_type = content_type
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches?(response)
|
14
|
+
super && headers.key?('Content-Type') && content_type.match(headers['Content-Type'])
|
15
|
+
end
|
16
|
+
|
17
|
+
def description
|
18
|
+
%Q{include 'Content-Type': '#{content_type}'}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rspec-api/matchers/filter/matcher'
|
2
|
+
|
3
|
+
module RSpecApi
|
4
|
+
module Matchers
|
5
|
+
module Filter
|
6
|
+
# Passes if the response body is a non-empty filtered JSON collection
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # Passes if the body only contains objects with ID = 1
|
11
|
+
# body = '[{"id": 1}, {"id": 1}, {"id": 1}]'
|
12
|
+
# expect(OpenStruct.new body: body).to be_filtered by: :id, value: 1
|
13
|
+
#
|
14
|
+
# For more examples check +be_filtered_spec.rb+.
|
15
|
+
def be_filtered(options = {})
|
16
|
+
RSpecApi::Matchers::Filter::Matcher.new options
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rspec-api/matchers/collection/matcher'
|
2
|
+
|
3
|
+
module RSpecApi
|
4
|
+
module Matchers
|
5
|
+
module Filter
|
6
|
+
class Matcher < Collection::Matcher
|
7
|
+
attr_accessor :field, :value, :compare_with
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@field = options[:by]
|
11
|
+
@value = options[:value]
|
12
|
+
@compare_with = options.fetch :compare_with, :==
|
13
|
+
end
|
14
|
+
|
15
|
+
def matches?(response)
|
16
|
+
super && all_objects_have_field? && has_two_objects? && is_filtered?
|
17
|
+
end
|
18
|
+
|
19
|
+
def description
|
20
|
+
if field
|
21
|
+
%Q(be filtered by #{field}#{compare_with}#{value})
|
22
|
+
else
|
23
|
+
%Q(be filtered)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def all_objects_have_field?
|
30
|
+
if field
|
31
|
+
json.all?{|item| item.is_a?(Hash) && item.key?(field)}
|
32
|
+
else
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_two_objects?
|
38
|
+
if json.length < 2
|
39
|
+
msg = "Cannot test filtering on an array with #{json.length} items"
|
40
|
+
raise RSpec::Core::Pending::PendingDeclaredInExample.new msg
|
41
|
+
else
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_filtered?
|
47
|
+
values = json.map{|item| item[field]}
|
48
|
+
values.all?{|v| v.send compare_with, value}
|
49
|
+
end
|
50
|
+
|
51
|
+
def reverse?
|
52
|
+
['desc', 'descending'].include? verse.to_s
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rspec-api/matchers/headers/matcher'
|
2
|
+
|
3
|
+
module RSpecApi
|
4
|
+
module Matchers
|
5
|
+
module Headers
|
6
|
+
# Passes if the response has a non-empty headers Hash.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # Passes if the headers include the content length
|
11
|
+
# headers = {'Content-Length' => 17372}
|
12
|
+
# expect(OpenStruct.new headers: headers).to have_headers
|
13
|
+
#
|
14
|
+
# For more examples check +have_headers_spec.rb+.
|
15
|
+
def have_headers
|
16
|
+
RSpecApi::Matchers::Headers::Matcher.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|