rspec_structure_matcher 0.0.5 → 0.0.6
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/.rspec +1 -0
- data/.ruby-version +1 -0
- data/README.md +17 -15
- data/lib/have_structure_matcher.rb +8 -31
- data/lib/rspec_structure_matcher.rb +1 -1
- data/lib/support_methods.rb +61 -0
- data/lib/version.rb +1 -1
- data/rspec_structure_matcher.gemspec +1 -0
- data/spec/lib/have_structure_matcher_spec.rb +162 -4
- data/spec/spec_helper.rb +4 -0
- metadata +22 -4
- data/lib/optionally_be_matcher.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f4c12a199c9b2b9e255d25a96ddf3d60ae7f776
|
4
|
+
data.tar.gz: d80fe7b3b712eff665815bb6e8bf7783b2143d33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c15985b65931ab5a1653f4439f4c9af78407d3cb007c5fb53f601c29dc9d68ee711794d3db19ed6df06e7cc6d562aaa9cb654b773ae0c2cfaffc2f2d3b92a33b
|
7
|
+
data.tar.gz: c9c2f33e1d0d7f0f69ae2a30717c343d21bfaeef64203f4c58e7561db59a3fb15a40bb057c81b00dd95b5463242c9c14b08fac4ab41d5004aed62543ad60cc54
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.1
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
A simple JSON structure matcher for RSpec.
|
4
4
|
|
5
|
-
When building an API it is nice to be able to test the response structure. Doing this with the built-in RSpec matchers can get tiresome. This matcher provides a nicer way to test for expected keys, value types,
|
5
|
+
When building an API it is nice to be able to test the response structure. Doing this with the built-in RSpec matchers can get tiresome. This matcher provides a nicer way to test for expected keys, value types, regular expressions, or custom validation procs/lambdas.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -23,11 +23,11 @@ Or install it yourself as:
|
|
23
23
|
Define an expected response structure:
|
24
24
|
|
25
25
|
expected_video_response = {
|
26
|
-
title:
|
27
|
-
episode_number:
|
26
|
+
title: 'Top Gear', # Exact match
|
27
|
+
episode_number: optionally(Fixnum), # Optional, may be null
|
28
28
|
tv_show: Hash,
|
29
29
|
published_on: /\d{4}-\d{2}-\d{2}/,
|
30
|
-
breadcrumbs:
|
30
|
+
breadcrumbs: ->(value) { value == 'bread/crumbs' },
|
31
31
|
images: Hash
|
32
32
|
}
|
33
33
|
|
@@ -42,31 +42,33 @@ Including an item in the expected structure ensures that a key with that name ex
|
|
42
42
|
Native Types (String, Hash, etc.)
|
43
43
|
: Test that the value matches the type, using `is_a?`.
|
44
44
|
|
45
|
-
Object
|
46
|
-
: Useful for testing the key exists but not requiring an particular type. Optional values (which are `null` if they are not present) can use this as well.
|
47
|
-
|
48
45
|
Regular Expression
|
49
46
|
: Tests the value for a match against the regular expression. Very useful for things like dates where your code is relying on a particular format.
|
50
47
|
|
51
|
-
|
48
|
+
Callable proc/lambda
|
49
|
+
: Callback your supplied proc with the `actual_value`. Return `true` for a match and `false` for a failure.
|
52
50
|
|
53
|
-
|
51
|
+
Exact match
|
52
|
+
: Other values will be compared directly with `==`.
|
54
53
|
|
55
|
-
|
54
|
+
### Testing Optional Values
|
56
55
|
|
57
|
-
|
56
|
+
As mentioned above, you can use `optionally` to test optional values, so that the test will pass even if the response contains a `null`. `optionally` is nothing more than a helpful lambda generation method, much like the proc/lambda that you can write yourself.
|
58
57
|
|
59
58
|
### Deep Structures
|
60
59
|
|
61
|
-
|
60
|
+
Nesting deeper structures works automatically. Simply nest your structure:
|
62
61
|
|
63
|
-
|
64
|
-
title:
|
62
|
+
expected_video_response = {
|
63
|
+
title: 'Episode 1',
|
64
|
+
tv_show: {
|
65
|
+
title: 'Top Gear'
|
66
|
+
}
|
65
67
|
}
|
66
68
|
|
67
69
|
And then compare the structure as normal:
|
68
70
|
|
69
|
-
expect(video
|
71
|
+
expect(video).to have_structure(expected_video_response)
|
70
72
|
|
71
73
|
## Contributing
|
72
74
|
|
@@ -1,38 +1,15 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
1
3
|
RSpec::Matchers.define :have_structure do |expected, opts|
|
2
4
|
match do |actual|
|
3
|
-
|
5
|
+
HaveStructureMatcher.match?(actual, expected)
|
4
6
|
end
|
5
7
|
|
6
8
|
failure_message do |actual|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
expected.each_with_object({}) do |(key, value), memo|
|
13
|
-
if !has_key?(actual, key) || !value_match(get_key(actual, key), value)
|
14
|
-
memo[key] = value
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def has_key?(actual, key)
|
20
|
-
actual.key?(key.to_s) || actual.key?(key.to_sym)
|
21
|
-
end
|
22
|
-
|
23
|
-
def get_key(actual, key)
|
24
|
-
if actual.key?(key.to_s)
|
25
|
-
actual[key.to_s]
|
26
|
-
else
|
27
|
-
actual[key.to_sym]
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def value_match(actual_value, expected_value)
|
32
|
-
if expected_value.is_a?(Regexp)
|
33
|
-
actual_value =~ expected_value
|
34
|
-
else
|
35
|
-
actual_value.is_a?(expected_value)
|
36
|
-
end
|
9
|
+
differ = RSpec::Support::Differ.new(color: RSpec::Matchers.configuration.color?)
|
10
|
+
differ.diff(
|
11
|
+
actual.pretty_inspect,
|
12
|
+
HaveStructureMatcher.build_diff(actual, expected).pretty_inspect
|
13
|
+
)
|
37
14
|
end
|
38
15
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module HaveStructureMatcher
|
2
|
+
module Methods
|
3
|
+
def optionally(optional_expected_value)
|
4
|
+
->(actual_value) { actual_value.nil? || HaveStructureMatcher.match?(actual_value, optional_expected_value) }
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.match?(actual, expected)
|
9
|
+
actual == build_diff(actual, expected)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.build_diff(actual, expected)
|
13
|
+
return expected unless actual.is_a?(Hash)
|
14
|
+
|
15
|
+
keys = actual.keys | expected.keys
|
16
|
+
|
17
|
+
keys.each_with_object({}) do |key, memo|
|
18
|
+
if actual.key?(key) && expected.key?(key)
|
19
|
+
actual_value = actual[key]
|
20
|
+
expected_value = expected[key]
|
21
|
+
|
22
|
+
if expected_value.is_a?(Hash)
|
23
|
+
memo[key] = build_diff(actual_value, expected_value)
|
24
|
+
elsif expected_value.is_a?(Array)
|
25
|
+
memo[key] = expected_value.zip(actual_value).map { |(e,a)| build_diff(a, e) }
|
26
|
+
elsif value_match?(actual_value, expected_value)
|
27
|
+
memo[key] = actual_value
|
28
|
+
else
|
29
|
+
memo[key] = print_expected_value(expected_value)
|
30
|
+
end
|
31
|
+
elsif expected.key?(key)
|
32
|
+
expected_value = expected[key]
|
33
|
+
memo[key] = print_expected_value(expected_value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.print_expected_value(expected_value)
|
39
|
+
if expected_value.respond_to?(:call)
|
40
|
+
"Proc [#{expected_value.source_location.join(':')}]"
|
41
|
+
else
|
42
|
+
expected_value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.value_match?(actual_value, expected_value)
|
47
|
+
if expected_value.is_a?(Regexp)
|
48
|
+
actual_value =~ expected_value
|
49
|
+
elsif expected_value.is_a?(Class)
|
50
|
+
actual_value.is_a?(expected_value)
|
51
|
+
elsif expected_value.respond_to?(:call)
|
52
|
+
expected_value.call(actual_value)
|
53
|
+
else
|
54
|
+
actual_value == expected_value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
RSpec.configure do |config|
|
60
|
+
config.include HaveStructureMatcher::Methods
|
61
|
+
end
|
data/lib/version.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'spec_helper'
|
1
2
|
require 'rspec_structure_matcher'
|
2
3
|
|
3
4
|
describe 'have_structure' do
|
@@ -18,7 +19,23 @@ describe 'have_structure' do
|
|
18
19
|
it 'raises the correct error' do
|
19
20
|
expect {
|
20
21
|
expect(structure).to have_structure(expected_structure)
|
21
|
-
}.to raise_error(RSpec::Expectations::ExpectationNotMetError,
|
22
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /-"bar" => String/)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when there too many keys' do
|
27
|
+
let(:structure) {
|
28
|
+
{
|
29
|
+
'foo' => 'baz',
|
30
|
+
'bar' => 'baz',
|
31
|
+
'bam' => 'baz'
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
it 'raises the correct error' do
|
36
|
+
expect {
|
37
|
+
expect(structure).to have_structure(expected_structure)
|
38
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /\+"bam" => "baz"/)
|
22
39
|
end
|
23
40
|
end
|
24
41
|
|
@@ -33,7 +50,7 @@ describe 'have_structure' do
|
|
33
50
|
it 'raises the correct error' do
|
34
51
|
expect {
|
35
52
|
expect(structure).to have_structure(expected_structure)
|
36
|
-
}.to raise_error(RSpec::Expectations::ExpectationNotMetError,
|
53
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /-"foo" => String/)
|
37
54
|
end
|
38
55
|
end
|
39
56
|
|
@@ -60,10 +77,151 @@ describe 'have_structure' do
|
|
60
77
|
}
|
61
78
|
}
|
62
79
|
|
63
|
-
it '
|
80
|
+
it 'fails validation' do
|
64
81
|
expect {
|
65
82
|
expect(structure).to have_structure(expected_structure)
|
66
|
-
}.
|
83
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /-"foo" => String/)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'matching exact values' do
|
88
|
+
let(:structure) do
|
89
|
+
{
|
90
|
+
'foo' => 1,
|
91
|
+
'bar' => 'baz'
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with correct values' do
|
96
|
+
let(:expected_structure) do
|
97
|
+
{
|
98
|
+
'foo' => 1,
|
99
|
+
'bar' => 'baz'
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'raise no error' do
|
104
|
+
expect {
|
105
|
+
expect(structure).to have_structure(expected_structure)
|
106
|
+
}.not_to raise_error
|
107
|
+
end
|
67
108
|
end
|
109
|
+
|
110
|
+
context 'with incorrect values' do
|
111
|
+
let(:expected_structure) do
|
112
|
+
{
|
113
|
+
'foo' => 2,
|
114
|
+
'bar' => 'baz'
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'fails validation' do
|
119
|
+
expect {
|
120
|
+
expect(structure).to have_structure(expected_structure)
|
121
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /-"foo" => 2/)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'matching with procs' do
|
127
|
+
let(:test_lambda) { ->(actual_value) { actual_value == 'baz' } }
|
128
|
+
|
129
|
+
let(:expected_structure) do
|
130
|
+
{
|
131
|
+
'foo' => test_lambda
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'correct value' do
|
136
|
+
let(:structure) { {'foo' => 'baz'} }
|
137
|
+
|
138
|
+
it 'raise no error' do
|
139
|
+
expect {
|
140
|
+
expect(structure).to have_structure(expected_structure)
|
141
|
+
}.not_to raise_error
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'incorrect value' do
|
146
|
+
let(:structure) { {'foo' => 100} }
|
147
|
+
|
148
|
+
it 'fails validation' do
|
149
|
+
expect {
|
150
|
+
expect(structure).to have_structure(expected_structure)
|
151
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /\+"foo" => 100/)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context "matching with 'optionally' helper method" do
|
157
|
+
let(:expected_structure) do
|
158
|
+
{
|
159
|
+
'foo' => optionally(2)
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'correct value' do
|
164
|
+
let(:structure) { {'foo' => 2} }
|
165
|
+
|
166
|
+
it 'raise no error' do
|
167
|
+
expect {
|
168
|
+
expect(structure).to have_structure(expected_structure)
|
169
|
+
}.not_to raise_error
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'null value' do
|
174
|
+
let(:structure) { {'foo' => nil} }
|
175
|
+
|
176
|
+
it 'raise no error' do
|
177
|
+
expect {
|
178
|
+
expect(structure).to have_structure(expected_structure)
|
179
|
+
}.not_to raise_error
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context "with nested structure" do
|
185
|
+
let(:expected_structure) do
|
186
|
+
{
|
187
|
+
'foo' => 1,
|
188
|
+
'bar' => {
|
189
|
+
'baz' => String
|
190
|
+
}
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
context "matches correctly" do
|
195
|
+
let(:structure) do
|
196
|
+
{
|
197
|
+
'foo' => 1,
|
198
|
+
'bar' => {
|
199
|
+
'baz' => 'hello'
|
200
|
+
}
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'passes validation' do
|
205
|
+
expect {
|
206
|
+
expect(structure).to have_structure(expected_structure)
|
207
|
+
}.not_to raise_error
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context "mis-matched nesting" do
|
212
|
+
let(:structure) do
|
213
|
+
{
|
214
|
+
'foo' => 1,
|
215
|
+
'bar' => 'a'
|
216
|
+
}
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'fails validation' do
|
220
|
+
expect {
|
221
|
+
expect(structure).to have_structure(expected_structure)
|
222
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /\+"bar" => "a"/)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
68
226
|
end
|
69
227
|
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec_structure_matcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Marklove
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry-byebug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: Rspec matchers for structured JSON responses. Compare expected keys,
|
56
70
|
value types, or even match values against regular expressions.
|
57
71
|
email:
|
@@ -61,16 +75,19 @@ extensions: []
|
|
61
75
|
extra_rdoc_files: []
|
62
76
|
files:
|
63
77
|
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- ".ruby-version"
|
64
80
|
- Gemfile
|
65
81
|
- LICENSE.txt
|
66
82
|
- README.md
|
67
83
|
- Rakefile
|
68
84
|
- lib/have_structure_matcher.rb
|
69
|
-
- lib/optionally_be_matcher.rb
|
70
85
|
- lib/rspec_structure_matcher.rb
|
86
|
+
- lib/support_methods.rb
|
71
87
|
- lib/version.rb
|
72
88
|
- rspec_structure_matcher.gemspec
|
73
89
|
- spec/lib/have_structure_matcher_spec.rb
|
90
|
+
- spec/spec_helper.rb
|
74
91
|
homepage: https://github.com/jjbananas/rspec_structure_matcher
|
75
92
|
licenses:
|
76
93
|
- MIT
|
@@ -91,9 +108,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
108
|
version: '0'
|
92
109
|
requirements: []
|
93
110
|
rubyforge_project:
|
94
|
-
rubygems_version: 2.
|
111
|
+
rubygems_version: 2.6.4
|
95
112
|
signing_key:
|
96
113
|
specification_version: 4
|
97
114
|
summary: Rspec matchers for structured JSON responses.
|
98
115
|
test_files:
|
99
116
|
- spec/lib/have_structure_matcher_spec.rb
|
117
|
+
- spec/spec_helper.rb
|