serial-spec 0.2.1 → 0.3.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 +1 -15
- data/lib/serial_spec.rb +16 -2
- data/lib/serial_spec/parsed_body.rb +94 -25
- data/lib/serial_spec/request_response.rb +5 -29
- data/lib/serial_spec/request_response/include_provide_matcher.rb +7 -0
- data/lib/serial_spec/request_response/provide_matcher.rb +19 -16
- data/lib/serial_spec/version.rb +1 -1
- data/serial-spec.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 042a2b062a70b8573348c2c0081eddf69e1394c0
|
4
|
+
data.tar.gz: 82c1fe7170df02c00c5208c92448c5bc41bab959
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a3e5711246fa8f02e5b48e86e37712c0efc45e1459a38c4e9fae3c2cdb1f8bb5e2b9366182a9e0cbd0aad9bb66c2b8f9705ccb662a814c8e538814bc5359716
|
7
|
+
data.tar.gz: 1bad6f37298e3aed3943f586a18afa436cf32a6a64f2cc33557a992bbf862325f3b33e01ad38e42ebdeb65e7a7bf7e550fbab2c542d92fc2388417896a59f646
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
SerialSpec is designed provide some simple matchers and macros overlaying a simple `rack/test` setup for testing json apis more effectively.
|
4
4
|
|
5
|
-
## Usage
|
5
|
+
## Basic Usage
|
6
6
|
|
7
7
|
```ruby
|
8
8
|
require "spec_helper"
|
@@ -18,20 +18,6 @@ RSpec.describe "test" do
|
|
18
18
|
end
|
19
19
|
```
|
20
20
|
|
21
|
-
## Testing architecture
|
22
|
-
|
23
|
-
SerialSpec is assuming that you are:
|
24
|
-
|
25
|
-
1. setting up preconditions
|
26
|
-
2. executing 1 request
|
27
|
-
3. inspecting the response
|
28
|
-
|
29
|
-
If you need more requests you should be using mocks and stubs.
|
30
|
-
|
31
|
-
### "But I need more than one request?"
|
32
|
-
|
33
|
-
SerialSpec, unlike rails' out of the box request testing system is closer to a controller test, than rails' request test, which is more kin to an integration test. Some people recommend using actual requests for performing things like login, auth vs mocking. However, this library doesn't subscribe to that pattern. If you aren't ok with that, you should look elsewhere.
|
34
|
-
|
35
21
|
## Contributing
|
36
22
|
|
37
23
|
1. Fork it ( https://github.com/blakechambers/serial-spec/fork )
|
data/lib/serial_spec.rb
CHANGED
@@ -15,9 +15,16 @@ module SerialSpec
|
|
15
15
|
include ItExpects
|
16
16
|
include RequestResponse
|
17
17
|
include RequestResponse::Helpers
|
18
|
+
|
18
19
|
if defined?(ActiveModel::Serializer)
|
19
20
|
require "serial_spec/request_response/provide_matcher"
|
20
21
|
include RequestResponse::ProvideMatcher
|
22
|
+
|
23
|
+
if defined?(::RSpec) and
|
24
|
+
defined?(::RSpec::Core::Version::STRING) and
|
25
|
+
Gem::Version.new(::RSpec::Core::Version::STRING) >= Gem::Version.new("3.2.0")
|
26
|
+
require "serial_spec/request_response/include_provide_matcher"
|
27
|
+
end
|
21
28
|
end
|
22
29
|
|
23
30
|
SERIAL_VALID_VERBS = %w{GET POST PUT PATCH DELETE OPTIONS HEAD}
|
@@ -33,8 +40,15 @@ module SerialSpec
|
|
33
40
|
if request_str.split(/\s+/).count == 2
|
34
41
|
request_method_string, request_path_str = request_str.split(/\s+/)
|
35
42
|
if SERIAL_VALID_VERBS.include?(request_method_string)
|
36
|
-
|
37
|
-
|
43
|
+
# Prefer preference to blocks, chances are the blocks need to be
|
44
|
+
# executed at a lower level
|
45
|
+
unless request_opts[:request_method] and request_opts[:request_method].instance_of?(InheritableAccessors::InheritableOptionAccessor::LetOption)
|
46
|
+
request_method request_method_string
|
47
|
+
end
|
48
|
+
|
49
|
+
unless request_opts[:request_path] and request_opts[:request_path].instance_of?(InheritableAccessors::InheritableOptionAccessor::LetOption)
|
50
|
+
request_path request_path_str
|
51
|
+
end
|
38
52
|
end
|
39
53
|
end
|
40
54
|
|
@@ -4,57 +4,126 @@ require "active_support/core_ext/hash/indifferent_access"
|
|
4
4
|
module SerialSpec
|
5
5
|
|
6
6
|
class ParsedBody
|
7
|
+
class MissingSelectorError < StandardError ; end
|
8
|
+
class NotAnEnumerableObject < StandardError ; end
|
7
9
|
|
8
|
-
|
9
|
-
attr_reader :selector
|
10
|
+
include Enumerable
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
attr_accessor :raw_body
|
13
|
+
attr_accessor :selector
|
14
|
+
attr_reader :body
|
15
|
+
|
16
|
+
def initialize(raw_body=nil, options={})
|
17
|
+
@selector = options[:selector] || []
|
18
|
+
@raw_body = raw_body
|
14
19
|
end
|
15
20
|
|
16
|
-
def
|
17
|
-
|
18
|
-
class_eval <<-METHOD
|
19
|
-
def #{methud.to_s}(*args)
|
20
|
-
selector.push([#{methud.to_sym}, args])
|
21
|
-
self
|
22
|
-
end
|
23
|
-
METHOD
|
24
|
-
end
|
21
|
+
def body
|
22
|
+
@body ||= JSON.parse(raw_body)
|
25
23
|
end
|
26
24
|
|
27
|
-
|
25
|
+
def each(&block)
|
26
|
+
current_obj = clone.execute
|
27
|
+
raise NotAnEnumerableObject unless current_obj.kind_of?(Hash) or current_obj.kind_of?(Array)
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
if current_obj.kind_of?(Array)
|
32
|
+
current_obj.each_with_index do |item, i|
|
33
|
+
yield(
|
34
|
+
clone.tap do |b|
|
35
|
+
b.selector.push([:[], i])
|
36
|
+
end
|
37
|
+
)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
current_obj.each do |key, value|
|
41
|
+
yield(
|
42
|
+
key,
|
43
|
+
clone.tap do |b|
|
44
|
+
b.selector.push([:[], key])
|
45
|
+
end
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
28
50
|
|
29
51
|
def [](*args)
|
30
|
-
|
31
|
-
|
52
|
+
clone.tap do |b|
|
53
|
+
b.selector.push([:[], *args])
|
54
|
+
end
|
32
55
|
end
|
33
56
|
|
34
57
|
def first(*args)
|
35
|
-
|
36
|
-
|
58
|
+
clone.tap do |b|
|
59
|
+
b.selector.push([:first])
|
60
|
+
end
|
37
61
|
end
|
38
62
|
|
39
63
|
def last(*args)
|
40
|
-
|
41
|
-
|
64
|
+
clone.tap do |b|
|
65
|
+
b.selector.push([:last])
|
66
|
+
end
|
42
67
|
end
|
43
68
|
|
44
69
|
def execute
|
45
70
|
copy = selector.clone
|
46
71
|
selector.clear
|
47
|
-
|
48
|
-
|
72
|
+
current_selector = []
|
73
|
+
|
74
|
+
failure = catch(:failed) do
|
75
|
+
|
76
|
+
return copy.inject(body) do |remainder, method_and_args|
|
77
|
+
current_selector << method_and_args
|
49
78
|
methud, *args = method_and_args
|
79
|
+
|
80
|
+
if [:first, :last].include?(methud) || args.first.kind_of?(Fixnum)
|
81
|
+
throw(:failed, [:expected_array, remainder, current_selector, method_and_args]) unless remainder.kind_of?(Array)
|
82
|
+
else
|
83
|
+
throw(:failed, [:expected_object, remainder, current_selector, method_and_args]) unless remainder.kind_of?(Hash)
|
84
|
+
end
|
85
|
+
|
50
86
|
if remainder.kind_of?(Hash)
|
51
87
|
remainder.with_indifferent_access.send methud, *args
|
52
88
|
else
|
53
89
|
remainder.send methud, *args
|
54
90
|
end
|
55
|
-
|
56
|
-
raise ArgumentError, "could not find '#{methud.inspect}' \nfor:\n '#{remainder.inspect}' \nin\n #{raw_body}"
|
91
|
+
|
57
92
|
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
if failure.kind_of?(Array)
|
97
|
+
raise MissingSelectorError, failed_message(*failure)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private ###############################
|
102
|
+
|
103
|
+
def initialize_copy(other)
|
104
|
+
@selector = other.selector.clone
|
105
|
+
@raw_body = other.raw_body
|
106
|
+
end
|
107
|
+
|
108
|
+
def formatted_selector(selector)
|
109
|
+
output = ""
|
110
|
+
|
111
|
+
selector.each do |item|
|
112
|
+
if item.first == :[]
|
113
|
+
output << "[#{item.last.inspect}]"
|
114
|
+
else
|
115
|
+
output << "[#{item.first.inspect}]"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
output
|
119
|
+
end
|
120
|
+
|
121
|
+
def failed_message(msg, remainder, selector, current_selector)
|
122
|
+
case msg
|
123
|
+
when :expected_object
|
124
|
+
"expected an object to have #{formatted_selector([current_selector])}, but found #{remainder.class}:\"#{remainder}\" at #{formatted_selector(selector[0..-2])}"
|
125
|
+
when :expected_array
|
126
|
+
"expected an array at \"#{formatted_selector(selector[0..-2])}\", but found #{remainder.class}:'#{remainder}'"
|
58
127
|
end
|
59
128
|
end
|
60
129
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require "rack/test"
|
2
2
|
require "inheritable_accessors/inheritable_hash_accessor"
|
3
|
+
require "inheritable_accessors/inheritable_option_accessor"
|
3
4
|
require "serial_spec/request_response/helpers"
|
4
5
|
|
5
6
|
module SerialSpec
|
@@ -7,15 +8,15 @@ module SerialSpec
|
|
7
8
|
extend ActiveSupport::Concern
|
8
9
|
include Rack::Test::Methods
|
9
10
|
include InheritableAccessors::InheritableHashAccessor
|
11
|
+
include InheritableAccessors::InheritableOptionAccessor
|
10
12
|
include Helpers
|
11
13
|
|
12
14
|
included do
|
13
|
-
include ::SerialSpec::RequestResponse::DSL
|
14
|
-
extend ::SerialSpec::RequestResponse::DSL
|
15
|
-
|
16
15
|
inheritable_hash_accessor :request_opts
|
17
16
|
inheritable_hash_accessor :request_params
|
18
17
|
inheritable_hash_accessor :request_envs
|
18
|
+
|
19
|
+
inheritable_option_accessor :request_path, :request_method, for: :request_opts
|
19
20
|
end
|
20
21
|
|
21
22
|
def perform_request!
|
@@ -25,30 +26,5 @@ module SerialSpec
|
|
25
26
|
current_session.send :process_request, request_path, env
|
26
27
|
end
|
27
28
|
|
28
|
-
module DSL
|
29
|
-
|
30
|
-
def request_path(new_path=nil)
|
31
|
-
if new_path
|
32
|
-
request_opts[:path] = new_path
|
33
|
-
else
|
34
|
-
path = request_opts[:path]
|
35
|
-
return path if path
|
36
|
-
raise "You must configure a path"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# GET, POST, PUT, DELETE, OPTIONS, HEAD
|
41
|
-
def request_method(new_method=nil)
|
42
|
-
if new_method
|
43
|
-
request_opts[:method] = new_method
|
44
|
-
else
|
45
|
-
methud = request_opts[:method]
|
46
|
-
return methud if methud
|
47
|
-
raise "You must configure a request method"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
29
|
end
|
54
|
-
end
|
30
|
+
end
|
@@ -13,7 +13,6 @@ module SerialSpec
|
|
13
13
|
class SerializerNotFound < StandardError ; end
|
14
14
|
|
15
15
|
attr_reader :as_serializer
|
16
|
-
attr_reader :with_root
|
17
16
|
attr_reader :expected
|
18
17
|
attr_reader :actual
|
19
18
|
|
@@ -22,11 +21,14 @@ module SerialSpec
|
|
22
21
|
def initialize(expected,options={})
|
23
22
|
@expected = expected
|
24
23
|
@as_serializer = options[:as]
|
25
|
-
|
24
|
+
|
25
|
+
if @as_serializer and not @as_serializer.instance_methods.include?(:serializable_hash)
|
26
|
+
raise ArgumentError, 'must be an active model serializer'
|
27
|
+
end
|
26
28
|
end
|
27
29
|
|
28
30
|
def actual_to_hash(actual)
|
29
|
-
if actual.kind_of? SerialSpec::ParsedBody
|
31
|
+
if actual.kind_of? SerialSpec::ParsedBody
|
30
32
|
strip_hypermedia(actual.execute)
|
31
33
|
else
|
32
34
|
strip_hypermedia(actual)
|
@@ -35,23 +37,23 @@ module SerialSpec
|
|
35
37
|
|
36
38
|
def resource_serializer
|
37
39
|
if as_serializer
|
38
|
-
as_serializer.new(expected, root:
|
40
|
+
as_serializer.new(expected, root: nil)
|
39
41
|
else
|
40
42
|
unless expected.respond_to?(:active_model_serializer)
|
41
43
|
throw(:failed, :serializer_not_specified_on_class)
|
42
44
|
end
|
43
|
-
expected.active_model_serializer.new(expected,root:
|
45
|
+
expected.active_model_serializer.new(expected, root: nil)
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
47
49
|
def collection_serializer
|
48
50
|
if as_serializer
|
49
|
-
ActiveModel::ArraySerializer.new(expected,serializer: as_serializer, root:
|
51
|
+
ActiveModel::ArraySerializer.new(expected, serializer: as_serializer, root: nil )
|
50
52
|
else
|
51
|
-
ActiveModel::ArraySerializer.new(expected, root:
|
53
|
+
ActiveModel::ArraySerializer.new(expected, root: nil)
|
52
54
|
end
|
53
55
|
end
|
54
|
-
|
56
|
+
|
55
57
|
# to_json first to normalize hash and all it's members
|
56
58
|
# the parse into JSON to compare to ParsedBody hash
|
57
59
|
def expected_to_hash
|
@@ -65,7 +67,7 @@ module SerialSpec
|
|
65
67
|
|
66
68
|
def normalize_data(data)
|
67
69
|
if data.kind_of?(Array)
|
68
|
-
data.each_with_index do |el,index|
|
70
|
+
data.each_with_index do |el, index|
|
69
71
|
data[index] = normalize_data(el)
|
70
72
|
end
|
71
73
|
elsif data.kind_of?(Hash)
|
@@ -74,17 +76,18 @@ module SerialSpec
|
|
74
76
|
end
|
75
77
|
|
76
78
|
def strip_hypermedia(actual)
|
77
|
-
actual.delete_if {|k,v| HYPERMEDIA_ATTRIBUTES.include?(k) }
|
79
|
+
(actual || {}).delete_if {|k,v| HYPERMEDIA_ATTRIBUTES.include?(k) }
|
78
80
|
end
|
79
81
|
|
80
82
|
def matches?(actual)
|
81
|
-
|
82
83
|
failure = catch(:failed) do
|
84
|
+
|
83
85
|
unless actual.kind_of?(Hash) || actual.kind_of?(Array) || actual.kind_of?(ParsedBody)
|
84
86
|
throw(:failed, :response_not_valid)
|
85
87
|
end
|
86
|
-
|
87
|
-
@
|
88
|
+
|
89
|
+
@actual = actual_to_hash(actual)
|
90
|
+
@expected = expected_to_hash
|
88
91
|
|
89
92
|
if @actual == @expected
|
90
93
|
#noop - specs pass
|
@@ -92,17 +95,17 @@ module SerialSpec
|
|
92
95
|
throw(:failed, :response_and_model_dont_match)
|
93
96
|
end
|
94
97
|
end
|
95
|
-
@failure_message = failed_message(failure) if failure
|
98
|
+
@failure_message = failed_message(failure) if failure
|
96
99
|
!failure
|
97
100
|
end
|
98
101
|
|
99
102
|
# when rspec asserts eq
|
100
103
|
alias == matches?
|
101
104
|
|
102
|
-
def failed_message(msg)
|
105
|
+
def failed_message(msg)
|
103
106
|
case msg
|
104
107
|
when :response_and_model_dont_match
|
105
|
-
"Actual and Expected do not match.\nActual #{actual}\nExpected #{expected}"
|
108
|
+
"Actual and Expected do not match.\nActual #{actual}\nExpected #{expected}"
|
106
109
|
when :serializer_not_specified_on_class
|
107
110
|
"'active_model_serializer' not implemented on expected, see ehttp://bit.ly/18TdmXs"
|
108
111
|
when :response_not_valid
|
data/lib/serial_spec/version.rb
CHANGED
data/serial-spec.gemspec
CHANGED
@@ -18,6 +18,6 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_runtime_dependency "inheritable_accessors", ">= 0.1.
|
21
|
+
spec.add_runtime_dependency "inheritable_accessors", ">= 0.1.2"
|
22
22
|
spec.add_runtime_dependency "activesupport", ">= 3.2.0"
|
23
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: serial-spec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Blake Chambers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inheritable_accessors
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.1.
|
19
|
+
version: 0.1.2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.1.
|
26
|
+
version: 0.1.2
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -63,6 +63,7 @@ files:
|
|
63
63
|
- lib/serial_spec/parsed_body.rb
|
64
64
|
- lib/serial_spec/request_response.rb
|
65
65
|
- lib/serial_spec/request_response/helpers.rb
|
66
|
+
- lib/serial_spec/request_response/include_provide_matcher.rb
|
66
67
|
- lib/serial_spec/request_response/provide_matcher.rb
|
67
68
|
- lib/serial_spec/version.rb
|
68
69
|
- serial-spec.gemspec
|