rspec_structure_matcher 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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