json_spec 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .rvmrc
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright © 2011 Steve Richert
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,224 @@
1
+ JsonSpec
2
+ ========
3
+ Easily handle JSON in RSpec and Cucumber
4
+
5
+ Installation
6
+ ------------
7
+ gem install json_spec
8
+
9
+ or with Bundler:
10
+
11
+ gem "json_spec"
12
+
13
+ Documentation
14
+ -------------
15
+ Please help write documentation!
16
+
17
+ [http://rdoc.info/github/collectiveidea/json_spec](http://rdoc.info/github/collectiveidea/json_spec)
18
+
19
+ Continuous Integration
20
+ ----------------------
21
+ Coming soon…
22
+
23
+ RSpec
24
+ --------------
25
+ JsonSpec defines four new RSpec matchers:
26
+
27
+ * `be_json_eql`
28
+ * `have_json_path`
29
+ * `have_json_type`
30
+ * `have_json_size`
31
+
32
+ The new matchers could be used in RSpec as follows:
33
+
34
+ describe User do
35
+ let(:user){ User.create!(:first_name => "Steve", :last_name => "Richert") }
36
+
37
+ context "#to_json" do
38
+ let(:json){ user.to_json }
39
+
40
+ it "includes the name" do
41
+ json.should be_json_eql(%({"first_name":"Steve","last_name":"Richert"})).excluding("friends")
42
+ end
43
+
44
+ it "includes the ID" do
45
+ json.should have_json_path("id")
46
+ json.should have_json_type(Integer).at_path("id")
47
+ end
48
+
49
+ it "includes friends" do
50
+ json.should have_json_size(0).at_path("friends")
51
+ user.friends << User.create!(:first_name => "Catie", :last_name => "Richert")
52
+ json.should have_json_size(1).at_path("friends")
53
+ end
54
+ end
55
+ end
56
+
57
+ ### Exclusions
58
+
59
+ JsonSpec ignores certain hash keys by default when comparing JSON:
60
+
61
+ * `id`
62
+ * `created_at`
63
+ * `updated_at`
64
+
65
+ It's oftentimes helpful when evaluating JSON representations of newly-created ActiveRecord records
66
+ so that the new ID and timestamps don't have to be known. These exclusions are globally
67
+ customizeable:
68
+
69
+ JsonSpec.configure do
70
+ exclude_keys "created_at", "updated_at"
71
+ end
72
+
73
+ Now, the `id` key will be included in JsonSpec's comparisons. Keys can also be excluded/included
74
+ per matcher by chaining the `excluding` or `including` methods (as shown above) which will add or subtract from
75
+ the globally excluded keys, respectively.
76
+
77
+ ### Paths
78
+
79
+ Each of JsonSpec's matchers deal with JSON "paths." These are simple strings of "/" separated
80
+ hash keys and array indexes. For instance, with the following JSON:
81
+
82
+ {
83
+ "first_name": "Steve",
84
+ "last_name": "Richert",
85
+ "friends": [
86
+ {
87
+ "first_name": "Catie",
88
+ "last_name": "Richert"
89
+ }
90
+ ]
91
+ }
92
+
93
+ We could access the first friend's first name with the path `"friends/0/first_name"`.
94
+
95
+ Cucumber
96
+ --------
97
+ JsonSpec provides Cucumber steps that utilize its RSpec matchers and that's where JsonSpec really
98
+ shines. This is perfect for testing your app's JSON API.
99
+
100
+ In order to use the Cucumber steps, in your `env.rb` you must:
101
+
102
+ require "json_spec/cucumber"
103
+
104
+ You also need to define a `last_json` method. If you're using Capybara, it could be as simple as:
105
+
106
+ def last_json
107
+ page.source
108
+ end
109
+
110
+ Now, you can use the JsonSpec steps in your features:
111
+
112
+ Feature: User API
113
+ Background:
114
+ Given the following users exist:
115
+ | id | first_name | last_name |
116
+ | 1 | Steve | Richert |
117
+ | 2 | Catie | Richert |
118
+ And "Steve Richert" is friends with "Catie Richert"
119
+
120
+ Scenario: Index action
121
+ When I visit "/users.json"
122
+ Then the JSON response should have 2 users
123
+ And the JSON response at "0/id" should be 1
124
+ And the JSON response at "1/id" should be 2
125
+
126
+ Scenario: Show action
127
+ When I visit "/users/1.json"
128
+ Then the JSON response at "first_name" should be "Steve"
129
+ And the JSON response at "last_name" should be "Richert"
130
+ And the JSON response should have "created_at"
131
+ And the JSON response at "created_at" should be a string
132
+ And the JSON response at "friends" should be:
133
+ """
134
+ [
135
+ {
136
+ "id": 2,
137
+ "first_name": "Catie",
138
+ "last_name": "Richert"
139
+ }
140
+ ]
141
+ """
142
+
143
+ The background steps above aren't provided by JsonSpec and the "visit" steps are provided by
144
+ Capybara. The remaining steps all stem from the five steps that JsonSpec provides. They're
145
+ versatile and can be used in plenty of different formats:
146
+
147
+ Then the JSON should be:
148
+ """
149
+ {
150
+ "key": "value"
151
+ }
152
+ """
153
+ Then the JSON at "path" should be:
154
+ """
155
+ [
156
+ "entry",
157
+ "entry"
158
+ ]
159
+ """
160
+
161
+ Then the JSON should be {"key":"value"}
162
+ Then the JSON at "path" should be {"key":"value"}
163
+ Then the JSON should be ["entry","entry"]
164
+ Then the JSON at "path" should be ["entry","entry"]
165
+ Then the JSON at "path" should be "string"
166
+ Then the JSON at "path" should be 10
167
+ Then the JSON at "path" should be 10.0
168
+ Then the JSON at "path" should be 1e+1
169
+ Then the JSON at "path" should be true
170
+ Then the JSON at "path" should be false
171
+ Then the JSON at "path" should be null
172
+
173
+ Then the JSON should have "path"
174
+
175
+ Then the JSON should be a hash
176
+ Then the JSON at "path" should be an array
177
+ Then the JSON at "path" should be a float
178
+
179
+ Then the JSON should have 1 entry
180
+ Then the JSON at "path" should have 2 entries
181
+ Then the JSON should have 3 keys
182
+ Then the JSON should have 4 whatevers
183
+
184
+ _All instances of "JSON" above could also be downcased and/or followed by "response."_
185
+
186
+ Contributing
187
+ ------------
188
+ In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html), **everyone** is encouraged to help improve this project.
189
+
190
+ Here are some ways *you* can contribute:
191
+
192
+ * using alpha, beta, and prerelease versions
193
+ * reporting bugs
194
+ * suggesting new features
195
+ * writing or editing documentation
196
+ * writing specifications
197
+ * writing code (**no patch is too small**: fix typos, add comments, clean up inconsistent whitespace)
198
+ * refactoring code
199
+ * closing [issues](https://github.com/collectiveidea/json_spec/issues)
200
+ * reviewing patches
201
+
202
+ Submitting an Issue
203
+ -------------------
204
+ We use the [GitHub issue tracker](https://github.com/collectiveidea/json_spec/issues) to track bugs
205
+ and features. Before submitting a bug report or feature request, check to make sure it hasn't already
206
+ been submitted. You can indicate support for an existing issuse by voting it up. When submitting a
207
+ bug report, please include a [Gist](https://gist.github.com/) that includes a stack trace and any
208
+ details that may be necessary to reproduce the bug, including your gem version, Ruby version, and
209
+ operating system. Ideally, a bug report should include a pull request with failing specs.
210
+
211
+ Submitting a Pull Request
212
+ -------------------------
213
+ 1. Fork the project.
214
+ 2. Create a topic branch.
215
+ 3. Implement your feature or bug fix.
216
+ 4. Add specs for your feature or bug fix.
217
+ 5. Run <tt>bundle exec rake</tt>. If your changes are not 100% covered and passing, go back to step 4.
218
+ 6. Commit and push your changes.
219
+ 7. Submit a pull request. Please do not include changes to the gemspec, version, or history file. (If you want to create your own version for some reason, please do so in a separate commit.)
220
+
221
+ Copyright
222
+ ---------
223
+ Copyright © 2011 Steve Richert
224
+ See [LICENSE](https://github.com/collectiveidea/json_spec/blob/master/LICENSE.md) for details.
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "cucumber/rake/task"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ Cucumber::Rake::Task.new(:cucumber)
7
+
8
+ task :test => [:spec, :cucumber]
9
+ task :default => :test
@@ -0,0 +1,233 @@
1
+ Feature: Equivalence
2
+ Background:
3
+ Given the JSON is:
4
+ """
5
+ {
6
+ "array": [
7
+ "json",
8
+ "spec"
9
+ ],
10
+ "created_at": "2011-07-08 02:27:34",
11
+ "empty_array": [
12
+
13
+ ],
14
+ "empty_hash": {
15
+ },
16
+ "false": false,
17
+ "float": 10.0,
18
+ "hash": {
19
+ "json": "spec"
20
+ },
21
+ "id": 1,
22
+ "integer": 10,
23
+ "negative": -10,
24
+ "null": null,
25
+ "string": "json_spec",
26
+ "true": true,
27
+ "updated_at": "2011-07-08 02:28:50"
28
+ }
29
+ """
30
+
31
+ Scenario: Identical JSON
32
+ When I get the JSON
33
+ Then the JSON should be:
34
+ """
35
+ {
36
+ "array": [
37
+ "json",
38
+ "spec"
39
+ ],
40
+ "created_at": "2011-07-08 02:27:34",
41
+ "empty_array": [
42
+
43
+ ],
44
+ "empty_hash": {
45
+ },
46
+ "false": false,
47
+ "float": 10.0,
48
+ "hash": {
49
+ "json": "spec"
50
+ },
51
+ "id": 1,
52
+ "integer": 10,
53
+ "negative": -10,
54
+ "null": null,
55
+ "string": "json_spec",
56
+ "true": true,
57
+ "updated_at": "2011-07-08 02:28:50"
58
+ }
59
+ """
60
+
61
+ Scenario: Reverse order
62
+ When I get the JSON
63
+ Then the JSON should be:
64
+ """
65
+ {
66
+ "updated_at": "2011-07-08 02:28:50",
67
+ "true": true,
68
+ "string": "json_spec",
69
+ "null": null,
70
+ "negative": -10,
71
+ "integer": 10,
72
+ "id": 1,
73
+ "hash": {
74
+ "json": "spec"
75
+ },
76
+ "float": 10.0,
77
+ "false": false,
78
+ "empty_hash": {
79
+ },
80
+ "empty_array": [
81
+
82
+ ],
83
+ "created_at": "2011-07-08 02:27:34",
84
+ "array": [
85
+ "json",
86
+ "spec"
87
+ ]
88
+ }
89
+ """
90
+
91
+ Scenario: Excluding keys
92
+ When I get the JSON
93
+ Then the JSON should be:
94
+ """
95
+ {
96
+ "array": [
97
+ "json",
98
+ "spec"
99
+ ],
100
+ "empty_array": [
101
+
102
+ ],
103
+ "empty_hash": {
104
+ },
105
+ "false": false,
106
+ "float": 10.0,
107
+ "hash": {
108
+ "json": "spec"
109
+ },
110
+ "integer": 10,
111
+ "negative": -10,
112
+ "null": null,
113
+ "string": "json_spec",
114
+ "true": true
115
+ }
116
+ """
117
+
118
+ Scenario: String
119
+ When I get the JSON
120
+ Then the JSON at "string" should be "json_spec"
121
+ And the JSON at "string" should be:
122
+ """
123
+ "json_spec"
124
+ """
125
+
126
+ Scenario: Integer
127
+ When I get the JSON
128
+ Then the JSON at "integer" should be 10
129
+ And the JSON at "integer" should be:
130
+ """
131
+ 10
132
+ """
133
+
134
+ Scenario: Negative integer
135
+ When I get the JSON
136
+ Then the JSON at "negative" should be -10
137
+ And the JSON at "negative" should be:
138
+ """
139
+ -10
140
+ """
141
+
142
+ Scenario: Float
143
+ When I get the JSON
144
+ Then the JSON at "float" should be 10.0
145
+ And the JSON at "float" should be 10.0e0
146
+ And the JSON at "float" should be 10.0e+0
147
+ And the JSON at "float" should be 10.0e-0
148
+ And the JSON at "float" should be 10e0
149
+ And the JSON at "float" should be 10e+0
150
+ And the JSON at "float" should be 10e-0
151
+ And the JSON at "float" should be 1.0e1
152
+ And the JSON at "float" should be 1.0e+1
153
+ And the JSON at "float" should be 1e1
154
+ And the JSON at "float" should be 1e+1
155
+ And the JSON at "float" should be 100.0e-1
156
+ And the JSON at "float" should be 100e-1
157
+ And the JSON at "float" should be:
158
+ """
159
+ 10.0
160
+ """
161
+
162
+ Scenario: Array
163
+ When I get the JSON
164
+ Then the JSON at "array" should be ["json","spec"]
165
+ And the JSON at "array/0" should be "json"
166
+ And the JSON at "array/1" should be "spec"
167
+ And the JSON at "array" should be:
168
+ """
169
+ [
170
+ "json",
171
+ "spec"
172
+ ]
173
+ """
174
+
175
+ Scenario: Empty array
176
+ When I get the JSON
177
+ Then the JSON at "empty_array" should be []
178
+ And the JSON at "empty_array" should be:
179
+ """
180
+ [
181
+
182
+ ]
183
+ """
184
+
185
+ Scenario: Hash
186
+ When I get the JSON
187
+ Then the JSON at "hash" should be {"json":"spec"}
188
+ And the JSON at "hash/json" should be "spec"
189
+ And the JSON at "hash" should be:
190
+ """
191
+ {
192
+ "json": "spec"
193
+ }
194
+ """
195
+
196
+ Scenario: Empty hash
197
+ When I get the JSON
198
+ Then the JSON at "empty_hash" should be {}
199
+ And the JSON at "empty_hash" should be:
200
+ """
201
+ {
202
+ }
203
+ """
204
+
205
+ Scenario: True
206
+ When I get the JSON
207
+ Then the JSON at "true" should be true
208
+ And the JSON at "true" should be:
209
+ """
210
+ true
211
+ """
212
+
213
+ Scenario: False
214
+ When I get the JSON
215
+ Then the JSON at "false" should be false
216
+ And the JSON at "false" should be:
217
+ """
218
+ false
219
+ """
220
+
221
+ Scenario: Null
222
+ When I get the JSON
223
+ Then the JSON at "null" should be null
224
+ And the JSON at "null" should be:
225
+ """
226
+ null
227
+ """
228
+
229
+ Scenario: Excluded value
230
+ When I get the JSON
231
+ Then the JSON at "created_at" should be "2011-07-08 02:27:34"
232
+ And the JSON at "id" should be 1
233
+ And the JSON at "updated_at" should be "2011-07-08 02:28:50"
@@ -0,0 +1,43 @@
1
+ Feature: Paths
2
+ Background:
3
+ Given the JSON is:
4
+ """
5
+ {
6
+ "array": [
7
+ {
8
+ "one": 1,
9
+ "two": 2
10
+ },
11
+ {
12
+ "four": 4,
13
+ "three": 3
14
+ }
15
+ ],
16
+ "hash": {
17
+ "even": [
18
+ 6,
19
+ 8
20
+ ],
21
+ "odd": [
22
+ 5,
23
+ 7
24
+ ]
25
+ },
26
+ "id": null
27
+ }
28
+ """
29
+
30
+ Scenario: Nested paths
31
+ When I get the JSON
32
+ Then the JSON should have "array/0/one"
33
+ And the JSON should have "array/0/two"
34
+ And the JSON should have "array/1/four"
35
+ And the JSON should have "array/1/three"
36
+ And the JSON should have "hash/even/0"
37
+ And the JSON should have "hash/even/1"
38
+ And the JSON should have "hash/odd/0"
39
+ And the JSON should have "hash/odd/1"
40
+
41
+ Scenario: Ignored paths
42
+ When I get the JSON
43
+ Then the JSON should have "id"
@@ -0,0 +1,38 @@
1
+ Feature: Sizes
2
+ Background:
3
+ Given the JSON is:
4
+ """
5
+ {
6
+ "id": null,
7
+ "one": [
8
+ 1
9
+ ],
10
+ "three": [
11
+ 1,
12
+ 2,
13
+ 3
14
+ ],
15
+ "two": [
16
+ 1,
17
+ 2
18
+ ],
19
+ "zero": [
20
+
21
+ ]
22
+ }
23
+ """
24
+
25
+ Scenario: Hash
26
+ When I get the JSON
27
+ Then the JSON should have 5 keys
28
+ And the JSON should have 5 values
29
+
30
+ Scenario: Empty array
31
+ When I get the JSON
32
+ Then the JSON at "zero" should have 0 entries
33
+
34
+ Scenario: Array
35
+ When I get the JSON
36
+ Then the JSON at "one" should have 1 entry
37
+ And the JSON at "two" should have 2 values
38
+ And the JSON at "three" should have 3 numbers
@@ -0,0 +1,7 @@
1
+ Given "the JSON is:" do |json|
2
+ @json = json
3
+ end
4
+
5
+ When "I get the JSON" do
6
+ @last_json = @json
7
+ end
@@ -0,0 +1,6 @@
1
+ $: << File.expand_path("../../../lib", __FILE__)
2
+ require "json_spec/cucumber"
3
+
4
+ def last_json
5
+ @last_json.to_s
6
+ end
@@ -0,0 +1,18 @@
1
+ Feature: Types
2
+ Scenario: All types
3
+ Given the JSON is:
4
+ """
5
+ {
6
+ "array": [],
7
+ "float": 10.0,
8
+ "hash": {},
9
+ "integer": 10,
10
+ "string": "json_spec"
11
+ }
12
+ """
13
+ When I get the JSON
14
+ Then the JSON should be a hash
15
+ And the JSON at "array" should be an array
16
+ And the JSON at "float" should be a float
17
+ And the JSON at "hash" should be a hash
18
+ And the JSON at "integer" should be an integer
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "json_spec/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "json_spec"
7
+ s.version = JsonSpec::VERSION
8
+ s.authors = ["Steve Richert"]
9
+ s.email = ["steve.richert@gmail.com"]
10
+ s.homepage = "https://github.com/collectiveidea/json_spec"
11
+ s.summary = "Easily handle JSON in RSpec and Cucumber"
12
+ s.description = "Easily handle JSON in RSpec and Cucumber"
13
+
14
+ s.rubyforge_project = "json_spec"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency "json", "~> 1.0"
22
+ s.add_dependency "rspec", "~> 2.0"
23
+
24
+ s.add_development_dependency "cucumber", "~> 1.0"
25
+ end
@@ -0,0 +1,9 @@
1
+ require "json_spec/version"
2
+ require "json_spec/errors"
3
+ require "json_spec/configuration"
4
+ require "json_spec/matchers"
5
+ require "json_spec/helpers"
6
+
7
+ module JsonSpec
8
+ extend Configuration
9
+ end
@@ -0,0 +1,27 @@
1
+ require "set"
2
+
3
+ module JsonSpec
4
+ module Configuration
5
+ DEFAULT_EXCLUDED_KEYS = %w(id created_at updated_at)
6
+
7
+ def configure(&block)
8
+ instance_eval(&block)
9
+ end
10
+
11
+ def excluded_keys
12
+ @excluded_keys ||= DEFAULT_EXCLUDED_KEYS
13
+ end
14
+
15
+ def excluded_keys=(keys)
16
+ @excluded_keys = keys.map{|k| k.to_s }.uniq
17
+ end
18
+
19
+ def exclude_keys(*keys)
20
+ self.excluded_keys = keys
21
+ end
22
+
23
+ def reset
24
+ instance_variables.each{|iv| remove_instance_variable(iv) }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path("../../json_spec", __FILE__)
2
+
3
+ World(JsonSpec::Helpers)
4
+
5
+ Then /^the (?:JSON|json)(?: response)?(?: at "(.*)")? should be:$/ do |path, json|
6
+ last_json.should be_json_eql(json).at_path(path)
7
+ end
8
+
9
+ Then /^the (?:JSON|json)(?: response)?(?: at "(.*)")? should be (".*"|\-?\d+(?:\.\d+)?(?:[eE][\+\-]?\d+)?|\[.*\]|\{.*\}|true|false|null)$/ do |path, value|
10
+ last_json.should be_json_eql(value).at_path(path)
11
+ end
12
+
13
+ Then /^the (?:JSON|json)(?: response)? should have "(.*)"$/ do |path|
14
+ last_json.should have_json_path(path)
15
+ end
16
+
17
+ Then /^the (?:JSON|json)(?: response)?(?: at "(.*)")? should be an? (.*)$/ do |path, type|
18
+ klass = Module.const_get(type.gsub(/^./){|x| x.upcase })
19
+ last_json.should have_json_type(klass).at_path(path)
20
+ end
21
+
22
+ Then /^the (?:JSON|json)(?: response)?(?: at "(.*)")? should have (\d+)/ do |path, size|
23
+ last_json.should have_json_size(size.to_i).at_path(path)
24
+ end
@@ -0,0 +1,13 @@
1
+ module JsonSpec
2
+ class MissingPathError < StandardError
3
+ attr_reader :path
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ def to_s
10
+ %(Missing JSON path "#{path}")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ module JsonSpec
2
+ module Helpers
3
+ extend self
4
+
5
+ def json_at_path(json, path)
6
+ pretty_json_value(ruby_at_json_path(json, path))
7
+ end
8
+
9
+ def pretty_json_value(ruby)
10
+ case ruby
11
+ when Hash, Array then JSON.pretty_generate(ruby)
12
+ when NilClass then "null"
13
+ else ruby.inspect
14
+ end
15
+ end
16
+
17
+ def ruby_at_json_path(json, path)
18
+ json_path_to_keys(path).inject(parse_json_value(json)) do |value, key|
19
+ case value
20
+ when Hash, Array then value.fetch(key){ missing_json_path!(path) }
21
+ else missing_json_path!(path)
22
+ end
23
+ end
24
+ end
25
+
26
+ def json_path_to_keys(path)
27
+ path.to_s.gsub(/(?:^\/|\/$)/, "").split("/").map{|k| k =~ /^\d+$/ ? k.to_i : k }
28
+ end
29
+
30
+ def parse_json_value(json)
31
+ JSON.parse(%({"root":#{json}}))["root"]
32
+ rescue JSON::ParserError
33
+ # Re-raise more appropriate parsing error
34
+ JSON.parse(json)
35
+ end
36
+
37
+ def missing_json_path!(path)
38
+ raise JsonSpec::MissingPathError.new(path)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,113 @@
1
+ require "json"
2
+ require "rspec"
3
+
4
+ RSpec::Matchers.define :be_json_eql do |expected_json|
5
+ include JsonSpec::Helpers
6
+
7
+ diffable
8
+
9
+ match do |actual_json|
10
+ @actual, @expected = scrub(actual_json, @path), [scrub(expected_json)]
11
+ @actual == @expected.first
12
+ end
13
+
14
+ chain :at_path do |path|
15
+ @path = path
16
+ end
17
+
18
+ chain :excluding do |*keys|
19
+ excluded_keys.add(*keys.map{|k| k.to_s })
20
+ end
21
+
22
+ chain :including do |*keys|
23
+ excluded_keys.subtract(keys.map{|k| k.to_s })
24
+ end
25
+
26
+ failure_message_for_should do
27
+ message = "Expected equivalent JSON"
28
+ message << %( at path "#{@path}") if @path
29
+ message
30
+ end
31
+
32
+ def scrub(json, path = nil)
33
+ ruby = path ? ruby_at_json_path(json, path) : parse_json_value(json)
34
+ pretty_json_value(exclude_keys(ruby)).chomp + "\n"
35
+ end
36
+
37
+ def exclude_keys(ruby)
38
+ case ruby
39
+ when Hash
40
+ ruby.sort.inject({}) do |hash, (key, value)|
41
+ hash[key] = exclude_keys(value) unless exclude_key?(key)
42
+ hash
43
+ end
44
+ when Array
45
+ ruby.map{|v| exclude_keys(v) }
46
+ else ruby
47
+ end
48
+ end
49
+
50
+ def exclude_key?(key)
51
+ excluded_keys.include?(key)
52
+ end
53
+
54
+ def excluded_keys
55
+ @excluded_keys ||= Set.new(JsonSpec.excluded_keys)
56
+ end
57
+ end
58
+
59
+ RSpec::Matchers.define :have_json_path do |path|
60
+ include JsonSpec::Helpers
61
+
62
+ match do |json|
63
+ begin
64
+ ruby_at_json_path(json, path)
65
+ true
66
+ rescue JsonSpec::MissingPathError
67
+ false
68
+ end
69
+ end
70
+
71
+ failure_message_for_should do
72
+ %(Expected JSON path "#{path}")
73
+ end
74
+ end
75
+
76
+ RSpec::Matchers.define :have_json_type do |klass|
77
+ include JsonSpec::Helpers
78
+
79
+ match do |json|
80
+ ruby = @path ? ruby_at_json_path(json, @path) : parse_json_value(json)
81
+ ruby.is_a?(klass)
82
+ end
83
+
84
+ chain :at_path do |path|
85
+ @path = path
86
+ end
87
+
88
+ failure_message_for_should do
89
+ message = "Expected JSON value type of #{klass}"
90
+ message << %( at path "#{@path}") if @path
91
+ message
92
+ end
93
+ end
94
+
95
+ RSpec::Matchers.define :have_json_size do |expected_size|
96
+ include JsonSpec::Helpers
97
+
98
+ match do |json|
99
+ ruby = @path ? ruby_at_json_path(json, @path) : parse_json_value(json)
100
+ actual_size = ruby.is_a?(Enumerable) ? ruby.size : 1
101
+ actual_size == expected_size
102
+ end
103
+
104
+ chain :at_path do |path|
105
+ @path = path
106
+ end
107
+
108
+ failure_message_for_should do
109
+ message = "Expected JSON value size of #{expected_size}"
110
+ message << %( at path "#{@path}") if @path
111
+ message
112
+ end
113
+ end
@@ -0,0 +1,3 @@
1
+ module JsonSpec
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe JsonSpec::Configuration do
4
+ it "excludes id and timestamps by default" do
5
+ JsonSpec.excluded_keys.should == ["id", "created_at", "updated_at"]
6
+ end
7
+
8
+ it "excludes custom keys" do
9
+ JsonSpec.exclude_keys("token")
10
+ JsonSpec.excluded_keys.should == ["token"]
11
+ end
12
+
13
+ it "excludes custom keys via setter" do
14
+ JsonSpec.excluded_keys = ["token"]
15
+ JsonSpec.excluded_keys.should == ["token"]
16
+ end
17
+
18
+ it "excludes custom keys via block" do
19
+ JsonSpec.configure{|c| c.exclude_keys("token") }
20
+ JsonSpec.excluded_keys.should == ["token"]
21
+ end
22
+
23
+ it "excludes custom keys via block setter" do
24
+ JsonSpec.configure{|c| c.excluded_keys = ["token"] }
25
+ JsonSpec.excluded_keys.should == ["token"]
26
+ end
27
+
28
+ it "excludes custom keys via instance-evaluated block" do
29
+ JsonSpec.configure{ exclude_keys("token") }
30
+ JsonSpec.excluded_keys.should == ["token"]
31
+ end
32
+
33
+ it "ensures its excluded keys are strings" do
34
+ JsonSpec.exclude_keys(:token)
35
+ JsonSpec.excluded_keys.should == ["token"]
36
+ end
37
+
38
+ it "ensures its excluded keys are unique" do
39
+ JsonSpec.exclude_keys("token", :token)
40
+ JsonSpec.excluded_keys.should == ["token"]
41
+ end
42
+
43
+ it "resets its excluded keys" do
44
+ original = JsonSpec.excluded_keys
45
+
46
+ JsonSpec.exclude_keys("token")
47
+ JsonSpec.excluded_keys.should_not == original
48
+
49
+ JsonSpec.reset
50
+ JsonSpec.excluded_keys.should == original
51
+ end
52
+ end
@@ -0,0 +1,108 @@
1
+ require "spec_helper"
2
+
3
+ describe "Matchers:" do
4
+ context "be_json_eql" do
5
+ it "matches identical JSON" do
6
+ %({"json":"spec"}).should be_json_eql(%({"json":"spec"}))
7
+ end
8
+
9
+ it "matches differently-formatted JSON" do
10
+ %({"json": "spec"}).should be_json_eql(%({"json":"spec"}))
11
+ end
12
+
13
+ it "matches out-of-order hashes" do
14
+ %({"laser":"lemon","json":"spec"}).should be_json_eql(%({"json":"spec","laser":"lemon"}))
15
+ end
16
+
17
+ it "doesn't match out-of-order arrays" do
18
+ %(["json","spec"]).should_not be_json_eql(%(["spec","json"]))
19
+ end
20
+
21
+ it "ignores excluded-by-default hash keys" do
22
+ JsonSpec.excluded_keys.should_not be_empty
23
+
24
+ actual = expected = {"json" => "spec"}
25
+ JsonSpec.excluded_keys.each{|k| actual[k] = k }
26
+ actual.to_json.should be_json_eql(expected.to_json)
27
+ end
28
+
29
+ it "ignores custom excluded hash keys" do
30
+ JsonSpec.exclude_keys("ignore")
31
+ %({"json":"spec","ignore":"please"}).should be_json_eql(%({"json":"spec"}))
32
+ end
33
+
34
+ it "ignores nested, excluded hash keys" do
35
+ JsonSpec.exclude_keys("ignore")
36
+ %({"json":"spec","please":{"ignore":"this"}}).should be_json_eql(%({"json":"spec","please":{}}))
37
+ end
38
+
39
+ it "ignores hash keys when included in the expected value" do
40
+ JsonSpec.exclude_keys("ignore")
41
+ %({"json":"spec","ignore":"please"}).should be_json_eql(%({"json":"spec","ignore":"this"}))
42
+ end
43
+
44
+ it "doesn't match Ruby-equivalent, JSON-inequivalent values" do
45
+ %({"one":1}).should_not be_json_eql(%({"one":1.0}))
46
+ end
47
+
48
+ it "excludes extra hash keys per matcher" do
49
+ JsonSpec.excluded_keys = %w(ignore)
50
+ %({"id":1,"json":"spec","ignore":"please"}).should be_json_eql(%({"id":"2","json":"spec","ignore":"this"})).excluding("id")
51
+ end
52
+
53
+ it "excludes extra hash keys given as symbols" do
54
+ JsonSpec.excluded_keys = []
55
+ %({"id":1,"json":"spec"}).should be_json_eql(%({"id":2,"json":"spec"})).excluding(:id)
56
+ end
57
+
58
+ it "includes globally-excluded hash keys per matcher" do
59
+ JsonSpec.excluded_keys = %w(id ignore)
60
+ %({"id":1,"json":"spec","ignore":"please"}).should_not be_json_eql(%({"id":"2","json":"spec","ignore":"this"})).including("id")
61
+ end
62
+
63
+ it "includes globally-included hash keys given as symbols" do
64
+ JsonSpec.excluded_keys = %w(id)
65
+ %({"id":1,"json":"spec"}).should_not be_json_eql(%({"id":2,"json":"spec"})).including(:id)
66
+ end
67
+ end
68
+
69
+ context "have_json_size" do
70
+ it "counts array entries" do
71
+ %([1,2,3]).should have_json_size(3)
72
+ end
73
+
74
+ it "counts null array entries" do
75
+ %([1,null,3]).should have_json_size(3)
76
+ end
77
+
78
+ it "counts hash key/value pairs" do
79
+ %({"one":1,"two":2,"three":3}).should have_json_size(3)
80
+ end
81
+
82
+ it "counts null hash values" do
83
+ %({"one":1,"two":null,"three":3}).should have_json_size(3)
84
+ end
85
+ end
86
+
87
+ context "have_json_path" do
88
+ it "matches hash keys" do
89
+ %({"one":{"two":{"three":4}}}).should have_json_path("one/two/three")
90
+ end
91
+
92
+ it "doesn't match values" do
93
+ %({"one":{"two":{"three":4}}}).should_not have_json_path("one/two/three/4")
94
+ end
95
+
96
+ it "matches array indexes" do
97
+ %([1,[1,2,[1,2,3,4]]]).should have_json_path("1/2/3")
98
+ end
99
+
100
+ it "respects null array values" do
101
+ %([null,[null,null,[null,null,null,null]]]).should have_json_path("1/2/3")
102
+ end
103
+
104
+ it "matches hash keys and array indexes" do
105
+ %({"one":[1,2,{"three":4}]}).should have_json_path("one/2/three")
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe JsonSpec do
4
+ it "is awesome"
5
+ end
@@ -0,0 +1,7 @@
1
+ require "json_spec"
2
+
3
+ RSpec.configure do |config|
4
+ config.before do
5
+ JsonSpec.reset
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_spec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Steve Richert
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-07-08 00:00:00.000000000 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ requirement: &2153487840 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '1.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2153487840
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: &2153487340 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *2153487340
37
+ - !ruby/object:Gem::Dependency
38
+ name: cucumber
39
+ requirement: &2153486880 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: '1.0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2153486880
48
+ description: Easily handle JSON in RSpec and Cucumber
49
+ email:
50
+ - steve.richert@gmail.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - Gemfile
57
+ - LICENSE.md
58
+ - README.md
59
+ - Rakefile
60
+ - features/equivalence.feature
61
+ - features/paths.feature
62
+ - features/sizes.feature
63
+ - features/step_definitions/steps.rb
64
+ - features/support/env.rb
65
+ - features/types.feature
66
+ - json_spec.gemspec
67
+ - lib/json_spec.rb
68
+ - lib/json_spec/configuration.rb
69
+ - lib/json_spec/cucumber.rb
70
+ - lib/json_spec/errors.rb
71
+ - lib/json_spec/helpers.rb
72
+ - lib/json_spec/matchers.rb
73
+ - lib/json_spec/version.rb
74
+ - spec/json_spec/configuration_spec.rb
75
+ - spec/json_spec/matchers_spec.rb
76
+ - spec/json_spec_spec.rb
77
+ - spec/spec_helper.rb
78
+ has_rdoc: true
79
+ homepage: https://github.com/collectiveidea/json_spec
80
+ licenses: []
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project: json_spec
99
+ rubygems_version: 1.6.2
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Easily handle JSON in RSpec and Cucumber
103
+ test_files:
104
+ - features/equivalence.feature
105
+ - features/paths.feature
106
+ - features/sizes.feature
107
+ - features/step_definitions/steps.rb
108
+ - features/support/env.rb
109
+ - features/types.feature
110
+ - spec/json_spec/configuration_spec.rb
111
+ - spec/json_spec/matchers_spec.rb
112
+ - spec/json_spec_spec.rb
113
+ - spec/spec_helper.rb