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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 23536c95ca641e9f03faa3c2c7ba193ba9415b94
4
- data.tar.gz: 40b064c9a9cc9944b61eddf7f1628476dca36a2e
3
+ metadata.gz: 8f4c12a199c9b2b9e255d25a96ddf3d60ae7f776
4
+ data.tar.gz: d80fe7b3b712eff665815bb6e8bf7783b2143d33
5
5
  SHA512:
6
- metadata.gz: 0c2bb37e65de516a48748c6c1d0827471a71768db21fe201509d207f24118393dd0504ddd8a2f45c721b92ee09dae220a75692661c37a969d5994db2f55abc3b
7
- data.tar.gz: d001136a60f227e531825d8897f6c7b9e5611ba18bfd4d34b1963c58678e9457b8bef6216b5843b4efc3a5b20838911ce66b1019f78eb3b9296e58d091d9f19b
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, and even matching values against regular expressions.
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: String,
27
- episode_number: Object, # Optional, may be null
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: Array,
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
- ### Testing Optional Values
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
- As mentioned above, you can use `Object` to test optional values, so that the test will pass even if the response contains a `null`. To add an extra level of checking you can use the `optionally_be` matcher:
51
+ Exact match
52
+ : Other values will be compared directly with `==`.
54
53
 
55
- expect(video['episode_number']).to optionally_be(Integer)
54
+ ### Testing Optional Values
56
55
 
57
- This will pass if `video['episode_number']` is either `null` or `is_a?(Integer)`.
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
- Similar to optional values, testing deep strucutres has been kept as simple as possible. Simply define a new expected structure:
60
+ Nesting deeper structures works automatically. Simply nest your structure:
62
61
 
63
- tv_show_expected_structure = {
64
- title: String
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['tv_show']).to have_structure(tv_show_expected_structure)
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
- invalid_items(actual, expected).empty?
5
+ HaveStructureMatcher.match?(actual, expected)
4
6
  end
5
7
 
6
8
  failure_message do |actual|
7
- invalid = invalid_items(actual, expected)
8
- "missing or invalid keys: #{invalid.keys.join(', ')}\n\n#{invalid}"
9
- end
10
-
11
- def invalid_items(actual, expected)
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
@@ -1,2 +1,2 @@
1
+ require 'support_methods'
1
2
  require 'have_structure_matcher'
2
- require 'optionally_be_matcher'
@@ -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,3 @@
1
1
  module RspecStructureMatcher
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.3"
24
24
  spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "pry-byebug"
25
26
  end
@@ -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, /missing or invalid keys: bar/)
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, /missing or invalid keys: foo/)
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 'raise no error' do
80
+ it 'fails validation' do
64
81
  expect {
65
82
  expect(structure).to have_structure(expected_structure)
66
- }.not_to raise_error
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
@@ -0,0 +1,4 @@
1
+ require "bundler/setup"
2
+ Bundler.setup
3
+
4
+ require "pry-byebug"
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.5
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: 2014-12-16 00:00:00.000000000 Z
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.2.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
@@ -1,9 +0,0 @@
1
- RSpec::Matchers.define :optionally_be do |expected|
2
- match do |actual|
3
- actual.nil? || actual.is_a?(expected)
4
- end
5
-
6
- failure_message do |actual|
7
- "#{actual} must be 'nil' or '#{expected}'"
8
- end
9
- end