rspec-graphql_response 0.1.0 → 0.4.1
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/.github/workflows/test.yml +14 -0
- data/.gitignore +3 -0
- data/.gituprc +128 -0
- data/Gemfile.lock +16 -7
- data/README.md +64 -72
- data/RELEASE_NOTES.md +45 -0
- data/UPGRADE.md +41 -0
- data/docs/add_helper.md +94 -0
- data/docs/add_validator.md +168 -1
- data/docs/configuration.md +12 -0
- data/docs/execute_graphql.md +50 -0
- data/docs/graphql_spec_type.md +14 -0
- data/docs/have_operation.md +46 -0
- data/docs/operation.md +1 -36
- data/docs/response.md +1 -0
- data/docs/response_data.md +146 -0
- data/lib/rspec/graphql_response.rb +2 -1
- data/lib/rspec/graphql_response/dig_dug/dig_dug.rb +83 -0
- data/lib/rspec/graphql_response/helpers.rb +35 -7
- data/lib/rspec/graphql_response/helpers/execute_graphql.rb +14 -1
- data/lib/rspec/graphql_response/helpers/graphql_context.rb +3 -0
- data/lib/rspec/graphql_response/helpers/graphql_operation.rb +3 -0
- data/lib/rspec/graphql_response/helpers/graphql_variables.rb +3 -0
- data/lib/rspec/graphql_response/helpers/operation.rb +1 -0
- data/lib/rspec/graphql_response/helpers/response_data.rb +13 -0
- data/lib/rspec/graphql_response/matchers.rb +1 -0
- data/lib/rspec/graphql_response/matchers/have_errors.rb +2 -2
- data/lib/rspec/graphql_response/matchers/have_operation.rb +23 -0
- data/lib/rspec/graphql_response/validators.rb +6 -0
- data/lib/rspec/graphql_response/validators/have_operation.rb +24 -0
- data/lib/rspec/graphql_response/version.rb +1 -1
- data/rspec-graphql_response.gemspec +3 -2
- metadata +52 -21
- data/.travis.yml +0 -7
data/docs/response.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# The GraphQL Response, via Helper `response`
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# Using the `response_data` Helper
|
2
|
+
|
3
|
+
The `response_data` helper will dig through a graphql response, through
|
4
|
+
the outer hash, into the response data for an operation, and through any
|
5
|
+
and all layers of hash and array.
|
6
|
+
|
7
|
+
## Syntax
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
response_data *[dig_pattern]
|
11
|
+
```
|
12
|
+
|
13
|
+
Data returned via this helper will assume a `"data" => ` key at the root of
|
14
|
+
the `response` object. This root does not need to be specified in the list
|
15
|
+
of attributes for the `dig_pattern`.
|
16
|
+
|
17
|
+
### Params
|
18
|
+
|
19
|
+
* `*[dig_pattern]` - an array of attributes (`:symbol`, `"string"`, or `key: :value` pair) that describes
|
20
|
+
the data structure to dig through, and the final data set to retrieve from the graphql response.
|
21
|
+
|
22
|
+
#### dig_pattern
|
23
|
+
|
24
|
+
Each attribute added to the `dig_pattern` represents an attribute at the given level of the
|
25
|
+
data structure, in numeric order from left to right. The first attribute provides will dig into
|
26
|
+
that attribute at the first level of data (just below the `"data" =>` key). The second attribute
|
27
|
+
will dig through data just below that first level, etc. etc. etc.
|
28
|
+
|
29
|
+
For example, with a data structure as shown below, in "Basic Use", you could specifiy these
|
30
|
+
attributes for the dig pattern:
|
31
|
+
|
32
|
+
* :characters
|
33
|
+
* :name
|
34
|
+
|
35
|
+
Like this:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
response_data :characters, :name
|
39
|
+
```
|
40
|
+
|
41
|
+
This dig pattern will find the `"characters"` key just below `"data"`, then iterate through
|
42
|
+
the array of characters and retrieve the `"name"` of each character.
|
43
|
+
|
44
|
+
For more details and options for the dig pattern, see the examples below.
|
45
|
+
|
46
|
+
## Basic Use
|
47
|
+
|
48
|
+
A `response` data structure may look something like the following.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
{
|
52
|
+
"data" => {
|
53
|
+
"characters" => [
|
54
|
+
{ "id" => "1", "name" => "Jam" },
|
55
|
+
{ "id" => "2", "name" => "Redemption" },
|
56
|
+
{ "id" => "3", "name" => "Pet" }
|
57
|
+
]
|
58
|
+
}
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
The `response_data` helper will dig through to give you simplified
|
63
|
+
results that are easier to verify.
|
64
|
+
|
65
|
+
For example, if only the names of the characters need to be checked:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
response_data :characters, :name
|
69
|
+
|
70
|
+
# => ["Jam", "Redemption", "Pet"]
|
71
|
+
```
|
72
|
+
|
73
|
+
Or perhaps only the name for 2nd character is needed:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
response_data {characters: [1]}, :name
|
77
|
+
|
78
|
+
# => "Redemption"
|
79
|
+
```
|
80
|
+
|
81
|
+
## List Every Item in an Array
|
82
|
+
|
83
|
+
Many responses from a graphql call will include an array of data somewhere
|
84
|
+
in the data structure. If you need to return all of the items in an array,
|
85
|
+
you only need to specify that array's key:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
it "has characters" do
|
89
|
+
characters = response_data(:characters)
|
90
|
+
|
91
|
+
expect(character).to include(
|
92
|
+
{ id: 1, name: "Jam" },
|
93
|
+
# ...
|
94
|
+
)
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
## Dig a Field From Every Item in an Array
|
99
|
+
|
100
|
+
When validation only needs to occur on a specific field for items found in
|
101
|
+
an array, there are two options.
|
102
|
+
|
103
|
+
1. Specify a list of fields as already shown
|
104
|
+
2. change the array's key to a hash and provide a `:symbol` wrapped in an array as the value
|
105
|
+
|
106
|
+
The first option was already shown in the Basic Use section above.
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
response_data :characters, :name
|
110
|
+
|
111
|
+
# => ["Jam", "Redemption", "Pet"]
|
112
|
+
```
|
113
|
+
|
114
|
+
For the second option, the code would look like this:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
response_data characters: [:name]
|
118
|
+
|
119
|
+
# => ["Jam", "Redemption", "Pet"]
|
120
|
+
```
|
121
|
+
|
122
|
+
Both of these options are functionaly the same. The primary difference will be
|
123
|
+
how you wish to express the data structure in your code. Changing the list of
|
124
|
+
attributes to a hash with an array wrapping the value will provide a better
|
125
|
+
indication that an array is expected at that point in the data structure.
|
126
|
+
|
127
|
+
## Dig Out an Item By Index, From an Array
|
128
|
+
|
129
|
+
There may be times when only a single piece of a returned array needs to be
|
130
|
+
validated. To handle this, switch the key of the array to a hash, as in the
|
131
|
+
previous example. Rather than specifying a child node's key in the value, though,
|
132
|
+
specify the index of the item you wish to extract.
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
response_data characters: [1]
|
136
|
+
```
|
137
|
+
|
138
|
+
This will return the character at index 1, from the array of characters.
|
139
|
+
|
140
|
+
## Handling Nil
|
141
|
+
|
142
|
+
If there is no data the key supplied, the helper will return `nil`
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
response_data(:something_that_does_not_exist) #=> nil
|
146
|
+
```
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module RSpec
|
2
|
+
module GraphQLResponse
|
3
|
+
class DigDug
|
4
|
+
attr_reader :dig_pattern
|
5
|
+
|
6
|
+
def initialize(*dig_pattern)
|
7
|
+
@dig_pattern = parse_dig_pattern(*dig_pattern)
|
8
|
+
end
|
9
|
+
|
10
|
+
def dig(data)
|
11
|
+
dig_data(data, dig_pattern)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def dig_data(data, patterns)
|
17
|
+
return data if patterns.nil?
|
18
|
+
return data if patterns.empty?
|
19
|
+
|
20
|
+
node = patterns[0]
|
21
|
+
node_key = node[:key]
|
22
|
+
node_key = node_key.to_s if node_key.is_a? Symbol
|
23
|
+
node_value = node[:value]
|
24
|
+
|
25
|
+
if node[:type] == :symbol
|
26
|
+
result = dig_symbol(data, node_key)
|
27
|
+
elsif node[:type] == :array
|
28
|
+
if data.is_a? Hash
|
29
|
+
child_data = data[node_key]
|
30
|
+
result = dig_symbol(child_data, node_value)
|
31
|
+
elsif data.is_a? Array
|
32
|
+
result = data.map { |value|
|
33
|
+
child_data = value[node_key]
|
34
|
+
dig_symbol(child_data, node_value)
|
35
|
+
}.compact
|
36
|
+
else
|
37
|
+
result = data
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
dig_data(result, patterns.drop(1))
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_dig_pattern(*pattern)
|
45
|
+
pattern_config = pattern.map do |pattern_item|
|
46
|
+
if pattern_item.is_a? Symbol
|
47
|
+
{
|
48
|
+
type: :symbol,
|
49
|
+
key: pattern_item
|
50
|
+
}
|
51
|
+
elsif pattern_item.is_a? Hash
|
52
|
+
pattern_item.map do |key, value|
|
53
|
+
{
|
54
|
+
type: :array,
|
55
|
+
key: key,
|
56
|
+
value: value[0]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
pattern_config.flatten
|
63
|
+
end
|
64
|
+
|
65
|
+
def dig_symbol(data, key)
|
66
|
+
key = key.to_s if key.is_a? Symbol
|
67
|
+
return data[key] if data.is_a? Hash
|
68
|
+
|
69
|
+
if data.is_a? Array
|
70
|
+
if key.is_a? Numeric
|
71
|
+
mapped_data = data[key]
|
72
|
+
else
|
73
|
+
mapped_data = data.map { |value| value[key] }.flatten
|
74
|
+
end
|
75
|
+
|
76
|
+
return mapped_data
|
77
|
+
end
|
78
|
+
|
79
|
+
return data
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -1,23 +1,51 @@
|
|
1
1
|
module RSpec
|
2
2
|
module GraphQLResponse
|
3
|
-
def self.
|
3
|
+
def self.add_context_helper(name, &helper)
|
4
|
+
self.add_helper(name, scope: :context, &helper)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.add_helper(name, scope: :spec, &helper)
|
4
8
|
helper_module = Module.new do |mod|
|
5
|
-
mod.define_method(name) do |*args|
|
6
|
-
|
9
|
+
mod.define_method(name) do |*args, &block|
|
10
|
+
instance_var = "@#{name}".to_sym
|
11
|
+
|
12
|
+
if self.instance_variables.include? instance_var
|
13
|
+
return self.instance_variable_get(instance_var)
|
14
|
+
end
|
15
|
+
|
16
|
+
args << block
|
17
|
+
result = self.instance_exec(*args, &helper)
|
18
|
+
self.instance_variable_set(instance_var, result)
|
7
19
|
end
|
8
20
|
end
|
9
21
|
|
10
22
|
RSpec.configure do |config|
|
11
23
|
config.after(:each) do
|
12
|
-
|
24
|
+
instance_var = "@#{name}".to_sym
|
25
|
+
helper_module.instance_variable_set(instance_var, nil)
|
13
26
|
end
|
14
|
-
end
|
15
27
|
|
16
|
-
|
28
|
+
module_method = if scope == :spec
|
29
|
+
:include
|
30
|
+
elsif scope == :context
|
31
|
+
:extend
|
32
|
+
else
|
33
|
+
raise ArgumentError, "A helper method's scope must be either :spec or :describe"
|
34
|
+
end
|
35
|
+
|
36
|
+
config.send(module_method, helper_module, type: :graphql)
|
37
|
+
end
|
17
38
|
end
|
18
39
|
end
|
19
40
|
end
|
20
41
|
|
42
|
+
# describe level helpers
|
43
|
+
require_relative "helpers/graphql_context"
|
44
|
+
require_relative "helpers/graphql_operation"
|
45
|
+
require_relative "helpers/graphql_variables"
|
46
|
+
|
47
|
+
# spec level helpers
|
48
|
+
require_relative "helpers/execute_graphql"
|
21
49
|
require_relative "helpers/operation"
|
22
50
|
require_relative "helpers/response"
|
23
|
-
require_relative "helpers/
|
51
|
+
require_relative "helpers/response_data"
|
@@ -1,4 +1,17 @@
|
|
1
1
|
RSpec::GraphQLResponse.add_helper :execute_graphql do
|
2
2
|
config = RSpec::GraphQLResponse.configuration
|
3
|
-
|
3
|
+
|
4
|
+
operation = graphql_operation if respond_to? :graphql_operation
|
5
|
+
operation = self.instance_eval(&graphql_operation) if operation.is_a? Proc
|
6
|
+
|
7
|
+
operation_vars = graphql_variables if respond_to? :graphql_variables
|
8
|
+
operation_vars = self.instance_eval(&graphql_variables) if operation_vars.is_a? Proc
|
9
|
+
|
10
|
+
operation_context = graphql_context if respond_to? :graphql_context
|
11
|
+
operation_context = self.instance_eval(&operation_context) if operation_context.is_a? Proc
|
12
|
+
|
13
|
+
config.graphql_schema.execute(operation, {
|
14
|
+
variables: operation_vars,
|
15
|
+
context: operation_context
|
16
|
+
})
|
4
17
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
RSpec::GraphQLResponse.add_helper :response_data do |*fields|
|
2
|
+
next nil unless response.is_a? Hash
|
3
|
+
|
4
|
+
response_data = response["data"]
|
5
|
+
next nil if response_data.nil?
|
6
|
+
next nil if response_data.empty?
|
7
|
+
|
8
|
+
fields = fields.compact
|
9
|
+
next response_data if fields.empty?
|
10
|
+
|
11
|
+
dig_dug = RSpec::GraphQLResponse::DigDug.new(*fields)
|
12
|
+
dig_dug.dig(response_data)
|
13
|
+
end
|
@@ -11,7 +11,7 @@ RSpec::GraphQLResponse.add_matcher :have_errors do |count = nil|
|
|
11
11
|
@result.valid?
|
12
12
|
end
|
13
13
|
|
14
|
-
failure_message do |
|
14
|
+
failure_message do |_|
|
15
15
|
@result.reason
|
16
16
|
end
|
17
17
|
|
@@ -27,7 +27,7 @@ RSpec::GraphQLResponse.add_matcher :have_errors do |count = nil|
|
|
27
27
|
@result.valid?
|
28
28
|
end
|
29
29
|
|
30
|
-
failure_message_when_negated do |
|
30
|
+
failure_message_when_negated do |_|
|
31
31
|
@result.reason
|
32
32
|
end
|
33
33
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
RSpec::GraphQLResponse.add_matcher :have_operation do |operation_name|
|
2
|
+
match do |response|
|
3
|
+
validator = RSpec::GraphQLResponse.validator(:have_operation)
|
4
|
+
|
5
|
+
@result = validator.validate(response, operation_name: operation_name)
|
6
|
+
@result.valid?
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message do |_|
|
10
|
+
@result.reason
|
11
|
+
end
|
12
|
+
|
13
|
+
match_when_negated do |response|
|
14
|
+
validator = RSpec::GraphQLResponse.validator(:have_operation)
|
15
|
+
|
16
|
+
@result = validator.validate_negated(response, operation_name: operation_name)
|
17
|
+
@result.valid?
|
18
|
+
end
|
19
|
+
|
20
|
+
failure_message_when_negated do |_|
|
21
|
+
@result.reason
|
22
|
+
end
|
23
|
+
end
|
@@ -11,6 +11,11 @@ module RSpec
|
|
11
11
|
@validators[name] = validator_class
|
12
12
|
end
|
13
13
|
|
14
|
+
def self.remove_validator(name)
|
15
|
+
@validators ||= {}
|
16
|
+
@validators.delete(:key)
|
17
|
+
end
|
18
|
+
|
14
19
|
def self.validator(name)
|
15
20
|
@validators[name].new
|
16
21
|
end
|
@@ -18,3 +23,4 @@ module RSpec
|
|
18
23
|
end
|
19
24
|
|
20
25
|
require_relative "validators/have_errors"
|
26
|
+
require_relative "validators/have_operation"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
RSpec::GraphQLResponse.add_validator :have_operation do
|
2
|
+
failure_message :nil, "Cannot evaluate operations on nil"
|
3
|
+
failure_message :not_found, ->(expected, actual) { "Expected to find operation result named #{expected}, but did not find it\n\t#{actual}" }
|
4
|
+
|
5
|
+
validate do |response, operation_name:|
|
6
|
+
next fail_validation(:nil) unless response.is_a? Hash
|
7
|
+
|
8
|
+
op = response.dig("data", operation_name.to_s)
|
9
|
+
next fail_validation(:not_found, operation_name, response) if op.nil?
|
10
|
+
|
11
|
+
pass_validation
|
12
|
+
end
|
13
|
+
|
14
|
+
failure_message :found, ->(expected, actual) { "Expected not to find operation result named #{expected}, but found it\n\t#{actual}" }
|
15
|
+
|
16
|
+
validate_negated do |response, operation_name:|
|
17
|
+
next fail_validation(:nil) unless response.is_a? Hash
|
18
|
+
|
19
|
+
op = response.dig("data", operation_name.to_s)
|
20
|
+
next fail_validation(:found, operation_name, response) unless op.nil?
|
21
|
+
|
22
|
+
pass_validation
|
23
|
+
end
|
24
|
+
end
|