zenspec 0.1.0
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 +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +637 -0
- data/Rakefile +12 -0
- data/examples/progress_loader_demo.rb +113 -0
- data/lib/zenspec/formatters/progress_bar_formatter.rb +433 -0
- data/lib/zenspec/formatters/progress_formatter.rb +146 -0
- data/lib/zenspec/helpers/graphql_helpers.rb +103 -0
- data/lib/zenspec/helpers.rb +10 -0
- data/lib/zenspec/matchers/graphql_matchers.rb +358 -0
- data/lib/zenspec/matchers/graphql_type_matchers.rb +554 -0
- data/lib/zenspec/matchers/interactor_matchers.rb +216 -0
- data/lib/zenspec/matchers.rb +12 -0
- data/lib/zenspec/progress_loader.rb +155 -0
- data/lib/zenspec/railtie.rb +26 -0
- data/lib/zenspec/shoulda_config.rb +17 -0
- data/lib/zenspec/version.rb +5 -0
- data/lib/zenspec.rb +14 -0
- data/sig/zenspec.rbs +4 -0
- metadata +121 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zenspec
|
|
4
|
+
module Helpers
|
|
5
|
+
module GraphQLHelpers
|
|
6
|
+
# Execute a GraphQL query with variables and context
|
|
7
|
+
#
|
|
8
|
+
# @param query [String] The GraphQL query string
|
|
9
|
+
# @param variables [Hash] Query variables
|
|
10
|
+
# @param context [Hash] Execution context
|
|
11
|
+
# @param schema [GraphQL::Schema] Optional schema (defaults to AppSchema)
|
|
12
|
+
# @return [GraphQL::Query::Result] The query result
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# result = graphql_execute(query, variables: { id: "123" }, context: { current_user: user })
|
|
16
|
+
# expect(result).to succeed_graphql
|
|
17
|
+
#
|
|
18
|
+
def graphql_execute(query, variables: {}, context: {}, schema: nil)
|
|
19
|
+
schema_class = schema || default_schema
|
|
20
|
+
raise "No GraphQL schema found. Please define AppSchema or pass schema: argument" unless schema_class
|
|
21
|
+
|
|
22
|
+
schema_class.execute(
|
|
23
|
+
query,
|
|
24
|
+
variables: variables,
|
|
25
|
+
context: context
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Execute a GraphQL query with a user automatically added to context
|
|
30
|
+
#
|
|
31
|
+
# @param user [Object] The user object to add to context
|
|
32
|
+
# @param query [String] The GraphQL query string
|
|
33
|
+
# @param variables [Hash] Query variables
|
|
34
|
+
# @param context [Hash] Additional context (merged with user context)
|
|
35
|
+
# @param schema [GraphQL::Schema] Optional schema (defaults to AppSchema)
|
|
36
|
+
# @param context_key [Symbol] The key to use for the user in context (defaults to :current_user)
|
|
37
|
+
# @return [GraphQL::Query::Result] The query result
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# result = graphql_execute_as(user, query, variables: { id: "123" })
|
|
41
|
+
# expect(result).to succeed_graphql
|
|
42
|
+
#
|
|
43
|
+
def graphql_execute_as(user, query, variables: {}, context: {}, schema: nil, context_key: :current_user)
|
|
44
|
+
merged_context = context.merge(context_key => user)
|
|
45
|
+
graphql_execute(query, variables: variables, context: merged_context, schema: schema)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Execute a GraphQL mutation with input
|
|
49
|
+
#
|
|
50
|
+
# @param mutation [String] The GraphQL mutation string
|
|
51
|
+
# @param input [Hash] Mutation input variables
|
|
52
|
+
# @param context [Hash] Execution context
|
|
53
|
+
# @param schema [GraphQL::Schema] Optional schema (defaults to AppSchema)
|
|
54
|
+
# @return [GraphQL::Query::Result] The mutation result
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# result = graphql_mutate(mutation, input: { name: "John", email: "john@example.com" })
|
|
58
|
+
# expect(result).to succeed_graphql
|
|
59
|
+
#
|
|
60
|
+
def graphql_mutate(mutation, input: {}, context: {}, schema: nil)
|
|
61
|
+
graphql_execute(mutation, variables: { input: input }, context: context, schema: schema)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Execute a GraphQL mutation as a specific user
|
|
65
|
+
#
|
|
66
|
+
# @param user [Object] The user object to add to context
|
|
67
|
+
# @param mutation [String] The GraphQL mutation string
|
|
68
|
+
# @param input [Hash] Mutation input variables
|
|
69
|
+
# @param context [Hash] Additional context (merged with user context)
|
|
70
|
+
# @param schema [GraphQL::Schema] Optional schema (defaults to AppSchema)
|
|
71
|
+
# @param context_key [Symbol] The key to use for the user in context (defaults to :current_user)
|
|
72
|
+
# @return [GraphQL::Query::Result] The mutation result
|
|
73
|
+
#
|
|
74
|
+
# @example
|
|
75
|
+
# result = graphql_mutate_as(user, mutation, input: { name: "John" })
|
|
76
|
+
# expect(result).to succeed_graphql
|
|
77
|
+
#
|
|
78
|
+
def graphql_mutate_as(user, mutation, input: {}, context: {}, schema: nil, context_key: :current_user)
|
|
79
|
+
merged_context = context.merge(context_key => user)
|
|
80
|
+
graphql_mutate(mutation, input: input, context: merged_context, schema: schema)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def default_schema
|
|
86
|
+
# Try to find a schema in common locations
|
|
87
|
+
if defined?(AppSchema)
|
|
88
|
+
AppSchema
|
|
89
|
+
elsif defined?(Schema)
|
|
90
|
+
Schema
|
|
91
|
+
elsif defined?(GraphqlSchema)
|
|
92
|
+
GraphqlSchema
|
|
93
|
+
elsif defined?(ApplicationSchema)
|
|
94
|
+
ApplicationSchema
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
RSpec.configure do |config|
|
|
102
|
+
config.include Zenspec::Helpers::GraphQLHelpers
|
|
103
|
+
end
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rspec/expectations"
|
|
4
|
+
|
|
5
|
+
module Zenspec
|
|
6
|
+
module Matchers
|
|
7
|
+
module GraphQLMatchers
|
|
8
|
+
# Matcher for executing GraphQL queries and checking results
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# expect(query).to execute_graphql
|
|
12
|
+
# expect(query).to execute_graphql.with_variables(id: "123")
|
|
13
|
+
# expect(query).to execute_graphql.with_context(current_user: user)
|
|
14
|
+
# expect(query).to execute_graphql.and_succeed
|
|
15
|
+
# expect(query).to execute_graphql.and_succeed.returning_data("user", "id")
|
|
16
|
+
#
|
|
17
|
+
RSpec::Matchers.define :execute_graphql do
|
|
18
|
+
match do |query_string|
|
|
19
|
+
@variables ||= {}
|
|
20
|
+
@context ||= {}
|
|
21
|
+
|
|
22
|
+
# Execute the query
|
|
23
|
+
schema_class = defined?(AppSchema) ? AppSchema : GraphQL::Schema
|
|
24
|
+
@result = schema_class.execute(
|
|
25
|
+
query_string,
|
|
26
|
+
variables: @variables,
|
|
27
|
+
context: @context
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Check if we need to verify success
|
|
31
|
+
if @check_success
|
|
32
|
+
errors = @result["errors"]
|
|
33
|
+
return false if !errors.nil? && (errors.is_a?(Array) ? !errors.empty? : true)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Check if we need to return specific data
|
|
37
|
+
if @data_path&.any?
|
|
38
|
+
@returned_data = dig_data(@result["data"], @data_path)
|
|
39
|
+
true
|
|
40
|
+
else
|
|
41
|
+
@result["errors"].nil? || @result["errors"].empty?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
chain :with_variables do |variables|
|
|
46
|
+
@variables = variables
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
chain :with_context do |context|
|
|
50
|
+
@context = context
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
chain :and_succeed do
|
|
54
|
+
@check_success = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
chain :returning_data do |*path|
|
|
58
|
+
@data_path = path
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
failure_message do
|
|
62
|
+
errors = @result["errors"]
|
|
63
|
+
has_errors = !errors.nil? && (errors.is_a?(Array) ? !errors.empty? : true)
|
|
64
|
+
|
|
65
|
+
if has_errors
|
|
66
|
+
"expected GraphQL query to succeed, but got errors: #{@result["errors"]}"
|
|
67
|
+
elsif @data_path
|
|
68
|
+
"expected to find data at path #{@data_path.inspect}, but result was: #{@result["data"].inspect}"
|
|
69
|
+
else
|
|
70
|
+
"expected GraphQL query to execute without errors, but got: #{@result["errors"]}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Allow accessing the returned data
|
|
75
|
+
def returned_data
|
|
76
|
+
@returned_data
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def dig_data(data, path)
|
|
82
|
+
path.reduce(data) do |current, key|
|
|
83
|
+
return nil if current.nil?
|
|
84
|
+
|
|
85
|
+
current.is_a?(Hash) ? current[key.to_s] : nil
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Matcher for checking if GraphQL result has data at a specific path
|
|
91
|
+
#
|
|
92
|
+
# @example
|
|
93
|
+
# expect(result).to have_graphql_data
|
|
94
|
+
# expect(result).to have_graphql_data("user")
|
|
95
|
+
# expect(result).to have_graphql_data("user", "id")
|
|
96
|
+
# expect(result).to have_graphql_data("user", "id").with_value("123")
|
|
97
|
+
# expect(result).to have_graphql_data("user").matching(id: "123", name: "John")
|
|
98
|
+
# expect(result).to have_graphql_data("user").that_includes(name: "John")
|
|
99
|
+
# expect(result).to have_graphql_data("users").that_is_present
|
|
100
|
+
# expect(result).to have_graphql_data("deletedAt").that_is_null
|
|
101
|
+
# expect(result).to have_graphql_data("users").with_count(5)
|
|
102
|
+
#
|
|
103
|
+
RSpec::Matchers.define :have_graphql_data do |*path|
|
|
104
|
+
match do |result|
|
|
105
|
+
data = path.empty? ? result["data"] : dig_data(result["data"], path)
|
|
106
|
+
|
|
107
|
+
# Check for explicit null
|
|
108
|
+
if @check_null
|
|
109
|
+
return data.nil?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Check for presence (not nil and not empty)
|
|
113
|
+
if @check_present
|
|
114
|
+
return !data.nil? && (data.respond_to?(:empty?) ? !data.empty? : true)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Check count for arrays
|
|
118
|
+
if @expected_count
|
|
119
|
+
return false unless data.is_a?(Array)
|
|
120
|
+
return data.length == @expected_count
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check for partial matching (subset)
|
|
124
|
+
if @expected_match
|
|
125
|
+
return false unless data.is_a?(Hash)
|
|
126
|
+
return @expected_match.all? { |key, value| data[key.to_s] == value }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Check for inclusion (subset with any match)
|
|
130
|
+
if @expected_include
|
|
131
|
+
return false unless data.is_a?(Hash) || data.is_a?(Array)
|
|
132
|
+
|
|
133
|
+
if data.is_a?(Hash)
|
|
134
|
+
return @expected_include.all? { |key, value| data[key.to_s] == value }
|
|
135
|
+
else
|
|
136
|
+
# For arrays, check if any element matches
|
|
137
|
+
return data.any? do |item|
|
|
138
|
+
item.is_a?(Hash) && @expected_include.all? { |key, value| item[key.to_s] == value }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Check exact value
|
|
144
|
+
if @expected_value
|
|
145
|
+
data == @expected_value
|
|
146
|
+
else
|
|
147
|
+
!data.nil?
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
chain :with_value do |expected_value|
|
|
152
|
+
@expected_value = expected_value
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
chain :matching do |expected_hash|
|
|
156
|
+
@expected_match = expected_hash
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
chain :that_includes do |expected_hash|
|
|
160
|
+
@expected_include = expected_hash
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
chain :that_is_present do
|
|
164
|
+
@check_present = true
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
chain :that_is_null do
|
|
168
|
+
@check_null = true
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
chain :with_count do |expected_count|
|
|
172
|
+
@expected_count = expected_count
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
failure_message do |result|
|
|
176
|
+
data = path.empty? ? result["data"] : dig_data(result["data"], path)
|
|
177
|
+
|
|
178
|
+
if @check_null
|
|
179
|
+
"expected data at #{path.inspect} to be null, but got: #{data.inspect}"
|
|
180
|
+
elsif @check_present
|
|
181
|
+
"expected data at #{path.inspect} to be present, but it was #{data.inspect}"
|
|
182
|
+
elsif @expected_count
|
|
183
|
+
actual_count = data.is_a?(Array) ? data.length : "not an array"
|
|
184
|
+
"expected data at #{path.inspect} to have count #{@expected_count}, got #{actual_count}"
|
|
185
|
+
elsif @expected_match
|
|
186
|
+
mismatches = @expected_match.reject { |key, value| data.is_a?(Hash) && data[key.to_s] == value }
|
|
187
|
+
"expected data at #{path.inspect} to match #{@expected_match.inspect}, " \
|
|
188
|
+
"but #{mismatches.inspect} did not match. Got: #{data.inspect}"
|
|
189
|
+
elsif @expected_include
|
|
190
|
+
"expected data at #{path.inspect} to include #{@expected_include.inspect}, got: #{data.inspect}"
|
|
191
|
+
elsif path.empty?
|
|
192
|
+
"expected result to have data, but got: #{result.inspect}"
|
|
193
|
+
elsif @expected_value
|
|
194
|
+
"expected data at #{path.inspect} to be #{@expected_value.inspect}, got #{data.inspect}"
|
|
195
|
+
else
|
|
196
|
+
"expected to find data at path #{path.inspect}, but result was: #{result["data"].inspect}"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def dig_data(data, path)
|
|
201
|
+
path.reduce(data) do |current, key|
|
|
202
|
+
return nil if current.nil?
|
|
203
|
+
|
|
204
|
+
current.is_a?(Hash) ? current[key.to_s] : nil
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Matcher for checking if GraphQL result has errors
|
|
210
|
+
#
|
|
211
|
+
# @example
|
|
212
|
+
# expect(result).to have_graphql_errors
|
|
213
|
+
# expect(result).to have_graphql_error.with_message("Not found")
|
|
214
|
+
# expect(result).to have_graphql_error.with_extensions(code: "NOT_FOUND")
|
|
215
|
+
# expect(result).to have_graphql_error.at_path(["user", "email"])
|
|
216
|
+
# expect { execute_query }.to have_graphql_errors
|
|
217
|
+
#
|
|
218
|
+
RSpec::Matchers.define :have_graphql_errors do
|
|
219
|
+
match do |result_or_block|
|
|
220
|
+
if result_or_block.is_a?(Proc)
|
|
221
|
+
begin
|
|
222
|
+
result_or_block.call
|
|
223
|
+
@raised_error = false
|
|
224
|
+
false
|
|
225
|
+
rescue StandardError => e
|
|
226
|
+
@raised_error = true
|
|
227
|
+
@error = e
|
|
228
|
+
true
|
|
229
|
+
end
|
|
230
|
+
else
|
|
231
|
+
@result = result_or_block
|
|
232
|
+
errors = result_or_block["errors"]
|
|
233
|
+
return false if errors.nil? || (errors.is_a?(Array) && errors.empty?)
|
|
234
|
+
|
|
235
|
+
# Check for specific message
|
|
236
|
+
if @expected_message
|
|
237
|
+
return errors.any? { |error| error["message"]&.include?(@expected_message) }
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Check for specific extensions
|
|
241
|
+
if @expected_extensions
|
|
242
|
+
return errors.any? do |error|
|
|
243
|
+
extensions = error["extensions"]
|
|
244
|
+
extensions && @expected_extensions.all? { |key, value| extensions[key.to_s] == value }
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Check for specific path
|
|
249
|
+
if @expected_path
|
|
250
|
+
return errors.any? { |error| error["path"] == @expected_path }
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
true
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
chain :with_message do |expected_message|
|
|
258
|
+
@expected_message = expected_message
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
chain :with_extensions do |expected_extensions|
|
|
262
|
+
@expected_extensions = expected_extensions
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
chain :at_path do |expected_path|
|
|
266
|
+
@expected_path = expected_path
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
failure_message do
|
|
270
|
+
if @raised_error
|
|
271
|
+
"expected no errors, but got: #{@error.message}"
|
|
272
|
+
elsif @expected_message
|
|
273
|
+
errors = @result["errors"] || []
|
|
274
|
+
messages = errors.map { |e| e["message"] }
|
|
275
|
+
"expected errors to include message containing #{@expected_message.inspect}, " \
|
|
276
|
+
"got messages: #{messages.inspect}"
|
|
277
|
+
elsif @expected_extensions
|
|
278
|
+
errors = @result["errors"] || []
|
|
279
|
+
extensions = errors.map { |e| e["extensions"] }
|
|
280
|
+
"expected errors to have extensions #{@expected_extensions.inspect}, " \
|
|
281
|
+
"got extensions: #{extensions.inspect}"
|
|
282
|
+
elsif @expected_path
|
|
283
|
+
errors = @result["errors"] || []
|
|
284
|
+
paths = errors.map { |e| e["path"] }
|
|
285
|
+
"expected errors at path #{@expected_path.inspect}, got paths: #{paths.inspect}"
|
|
286
|
+
else
|
|
287
|
+
"expected GraphQL result to have errors, but it succeeded"
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
supports_block_expectations
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Alias for singular form
|
|
295
|
+
RSpec::Matchers.alias_matcher :have_graphql_error, :have_graphql_errors
|
|
296
|
+
|
|
297
|
+
# Matcher for checking if GraphQL query succeeds
|
|
298
|
+
#
|
|
299
|
+
# @example
|
|
300
|
+
# expect(result).to succeed_graphql
|
|
301
|
+
#
|
|
302
|
+
RSpec::Matchers.define :succeed_graphql do
|
|
303
|
+
match do |result|
|
|
304
|
+
result["errors"].nil? || result["errors"].empty?
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
failure_message do |result|
|
|
308
|
+
"expected GraphQL query to succeed, but got errors: #{result["errors"]}"
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Matcher for checking if GraphQL result has a specific field
|
|
313
|
+
#
|
|
314
|
+
# @example
|
|
315
|
+
# expect(result.data).to have_graphql_field("id")
|
|
316
|
+
# expect(result.data).to have_graphql_field(:name)
|
|
317
|
+
#
|
|
318
|
+
RSpec::Matchers.define :have_graphql_field do |field_name|
|
|
319
|
+
match do |data|
|
|
320
|
+
data.is_a?(Hash) && data.key?(field_name.to_s)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
failure_message do |data|
|
|
324
|
+
"expected data to have field #{field_name.inspect}, but data was: #{data.inspect}"
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Matcher for checking if GraphQL result has multiple fields with specific values
|
|
329
|
+
#
|
|
330
|
+
# @example
|
|
331
|
+
# expect(result.data).to have_graphql_fields(id: "123", name: "John")
|
|
332
|
+
#
|
|
333
|
+
RSpec::Matchers.define :have_graphql_fields do |expected_fields|
|
|
334
|
+
match do |data|
|
|
335
|
+
return false unless data.is_a?(Hash)
|
|
336
|
+
|
|
337
|
+
expected_fields.all? do |field, value|
|
|
338
|
+
data.key?(field.to_s) && data[field.to_s] == value
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
failure_message do |data|
|
|
343
|
+
missing_or_wrong = expected_fields.reject do |field, value|
|
|
344
|
+
data.key?(field.to_s) && data[field.to_s] == value
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
"expected data to have fields #{expected_fields.inspect}, " \
|
|
348
|
+
"but #{missing_or_wrong.inspect} did not match. " \
|
|
349
|
+
"Data was: #{data.inspect}"
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
RSpec.configure do |config|
|
|
357
|
+
config.include Zenspec::Matchers::GraphQLMatchers
|
|
358
|
+
end
|