rspec-api-matchers 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/rspec-api/rspec-api-matchers.png?branch=master)](https://travis-ci.org/rspec-api/rspec-api-matchers)
|
11
11
|
[![Code Climate](https://codeclimate.com/github/rspec-api/rspec-api-matchers.png)](https://codeclimate.com/github/rspec-api/rspec-api-matchers)
|
12
12
|
[![Coverage Status](https://coveralls.io/repos/rspec-api/rspec-api-matchers/badge.png)](https://coveralls.io/r/rspec-api/rspec-api-matchers)
|
13
13
|
[![Dependency Status](https://gemnasium.com/rspec-api/rspec-api-matchers.png)](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
|