spec_forge 0.1.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +67 -2
- data/README.md +366 -143
- data/flake.nix +2 -2
- data/lib/spec_forge/attribute/chainable.rb +19 -16
- data/lib/spec_forge/attribute/factory.rb +6 -18
- data/lib/spec_forge/attribute/faker.rb +56 -20
- data/lib/spec_forge/attribute/literal.rb +4 -0
- data/lib/spec_forge/attribute/resolvable.rb +4 -6
- data/lib/spec_forge/attribute/resolvable_array.rb +4 -0
- data/lib/spec_forge/attribute/resolvable_hash.rb +4 -0
- data/lib/spec_forge/attribute/variable.rb +6 -13
- data/lib/spec_forge/attribute.rb +3 -12
- data/lib/spec_forge/cli/init.rb +2 -9
- data/lib/spec_forge/configuration.rb +58 -0
- data/lib/spec_forge/factory.rb +4 -4
- data/lib/spec_forge/http/backend.rb +42 -8
- data/lib/spec_forge/http/client.rb +2 -2
- data/lib/spec_forge/http/request.rb +11 -14
- data/lib/spec_forge/normalizer/configuration.rb +77 -0
- data/lib/spec_forge/normalizer/expectation.rb +1 -0
- data/lib/spec_forge/normalizer/spec.rb +1 -0
- data/lib/spec_forge/normalizer.rb +14 -11
- data/lib/spec_forge/runner.rb +98 -72
- data/lib/spec_forge/spec/expectation/constraint.rb +2 -5
- data/lib/spec_forge/spec/expectation.rb +32 -13
- data/lib/spec_forge/spec.rb +20 -14
- data/lib/spec_forge/version.rb +1 -1
- data/lib/spec_forge.rb +20 -11
- data/lib/templates/forge_helper.tt +48 -0
- data/spec_forge/forge_helper.rb +37 -0
- data/spec_forge/specs/users.yml +6 -4
- metadata +7 -8
- data/lib/spec_forge/config.rb +0 -84
- data/lib/spec_forge/environment.rb +0 -71
- data/lib/spec_forge/normalizer/config.rb +0 -104
- data/lib/templates/config.tt +0 -19
- data/spec_forge/config.yml +0 -19
data/README.md
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
# SpecForge
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
---
|
3
|
+
[](https://badge.fury.io/rb/spec_forge)
|
4
|
+
[](https://github.com/itsthedevman/spec_forge/actions/workflows/main.yml)
|
5
|
+

|
8
6
|
|
9
7
|
Write API tests in YAML that read like documentation:
|
10
8
|
|
11
9
|
```yaml
|
12
10
|
user_profile:
|
13
11
|
path: /users/1
|
14
|
-
|
15
|
-
|
12
|
+
expectations:
|
13
|
+
- expect:
|
14
|
+
status: 200
|
16
15
|
json:
|
17
16
|
name: kind_of.string
|
18
17
|
email: /@/
|
@@ -20,7 +19,38 @@ user_profile:
|
|
20
19
|
|
21
20
|
That's a complete test. No Ruby code, no configuration files, no HTTP client setup - just a clear description of what you're testing. Under the hood, you get all the power of RSpec's matchers, Faker's data generation, and FactoryBot's test objects.
|
22
21
|
|
23
|
-
|
22
|
+
## Why SpecForge?
|
23
|
+
|
24
|
+
SpecForge shines when you need:
|
25
|
+
|
26
|
+
1. **Accessible API Testing**: Non-developers can write and maintain tests without Ruby knowledge. The YAML syntax reads like documentation.
|
27
|
+
2. **Living Documentation**: Tests serve as clear, maintainable documentation of your API's expected behavior.
|
28
|
+
3. **Power Without Complexity**: Get the benefits of Ruby-based tests (dynamic data, factories, matchers) without writing Ruby code.
|
29
|
+
4. **Quick Setup**: Start testing APIs without configuring HTTP clients or writing boilerplate code.
|
30
|
+
5. **Gradual Adoption**: Use alongside your existing test suite. Keep complex tests in RSpec while making simple API tests more accessible.
|
31
|
+
|
32
|
+
## When Not to Use SpecForge
|
33
|
+
|
34
|
+
Consider alternatives when you need:
|
35
|
+
|
36
|
+
1. **Complex Ruby Logic**: If your tests require custom Ruby code for data transformations or validations.
|
37
|
+
2. **Complex Test Setup**: When you need intricate database states or specific service mocks.
|
38
|
+
3. **Custom Response Validation**: For validation logic beyond what matchers provide.
|
39
|
+
4. **Complex Non-JSON Testing**: While SpecForge handles basic XML/HTML responses (coming soon), complex validation might need specialized tools.
|
40
|
+
|
41
|
+
## Roadmap
|
42
|
+
|
43
|
+
Current development priorities:
|
44
|
+
- [ ] Support for running individual specs
|
45
|
+
- [ ] Array support for `json` expectations
|
46
|
+
- [ ] Negated matchers: `matcher.not`
|
47
|
+
- [ ] `create_list/build_list` factory strategies
|
48
|
+
- [ ] `transform.map` support
|
49
|
+
- [ ] Improved error handling
|
50
|
+
- [ ] XML/HTML response handling
|
51
|
+
- [ ] OpenAPI generation from tests
|
52
|
+
|
53
|
+
Have a feature request? Open an issue on GitHub!
|
24
54
|
|
25
55
|
## Table of Contents
|
26
56
|
|
@@ -30,33 +60,36 @@ But that's just scratching the surface.
|
|
30
60
|
- [Getting Started](#getting-started)
|
31
61
|
- [Writing Your First Test](#writing-your-first-test)
|
32
62
|
- [Configuration](#configuration)
|
33
|
-
- [
|
34
|
-
- [
|
35
|
-
- [
|
63
|
+
- [Basic Configuration](#basic-configuration)
|
64
|
+
- [Framework Integration](#framework-integration)
|
65
|
+
- [Factory Configuration](#factory-configuration)
|
66
|
+
- [Debug Configuration](#debug-configuration)
|
67
|
+
- [Test Framework Configuration](#test-framework-configuration)
|
68
|
+
- [Configuration Inheritance](#configuration-inheritance)
|
69
|
+
- [Writing Tests](#writing-tests)
|
36
70
|
- [Basic Structure](#basic-structure)
|
37
71
|
- [Testing Response Data](#testing-response-data)
|
38
72
|
- [Multiple Expectations](#multiple-expectations)
|
39
73
|
- [Request Data](#request-data)
|
74
|
+
- [Path Parameters](#path-parameters)
|
40
75
|
- [Dynamic Features](#dynamic-features)
|
41
76
|
- [Variables](#variables)
|
42
77
|
- [Transformations](#transformations)
|
43
|
-
- [
|
78
|
+
- [Chaining Support](#chaining-support)
|
79
|
+
- [Factory Support](#factory-support)
|
80
|
+
- [Automatic Discovery](#automatic-discovery)
|
81
|
+
- [Custom Factory Paths](#custom-factory-paths)
|
82
|
+
- [Build Strategies](#build-strategies)
|
83
|
+
- [YAML Factory Definitions](#yaml-factory-definitions)
|
44
84
|
- [RSpec Matchers](#rspec-matchers)
|
45
85
|
- ["be" namespace](#be-namespace)
|
46
86
|
- ["kind_of" namespace](#kind_of-namespace)
|
47
87
|
- ["matchers" namespace](#matchers-namespace)
|
48
|
-
- [
|
88
|
+
- [How Tests Work](#how-tests-work)
|
49
89
|
- [Contributing](#contributing)
|
50
90
|
- [License](#license)
|
51
91
|
- [Looking for a Software Engineer?](#looking-for-a-software-engineer)
|
52
92
|
|
53
|
-
## Features
|
54
|
-
|
55
|
-
- **Write Tests in YAML**: Create clear, maintainable API tests using a declarative YAML syntax
|
56
|
-
- **RSpec Integration**: Harness RSpec's powerful matcher system and reporting through an intuitive interface
|
57
|
-
- **Dynamic Test Data**: Generate realistic test data using Faker, transformations, and a flexible variable system
|
58
|
-
- **Factory Integration**: Seamless integration with FactoryBot for test data generation
|
59
|
-
|
60
93
|
## Compatibility
|
61
94
|
|
62
95
|
Currently tested on:
|
@@ -96,13 +129,7 @@ Or with bundle:
|
|
96
129
|
bundle exec spec_forge init
|
97
130
|
```
|
98
131
|
|
99
|
-
This creates the `spec_forge` directory
|
100
|
-
```
|
101
|
-
spec_forge/
|
102
|
-
config.yml # Global configuration
|
103
|
-
factories/ # Your factory definitions
|
104
|
-
specs/ # Your test specifications
|
105
|
-
```
|
132
|
+
This creates the `spec_forge` directory containing factory definitions, test specifications, and global configuration.
|
106
133
|
|
107
134
|
## Writing Your First Test
|
108
135
|
|
@@ -119,8 +146,7 @@ get_user:
|
|
119
146
|
path: /users/1
|
120
147
|
method: GET
|
121
148
|
expectations:
|
122
|
-
-
|
123
|
-
expect:
|
149
|
+
- expect:
|
124
150
|
status: 200
|
125
151
|
json:
|
126
152
|
id: 1
|
@@ -136,68 +162,163 @@ spec_forge run
|
|
136
162
|
|
137
163
|
## Configuration
|
138
164
|
|
139
|
-
|
165
|
+
### Basic Configuration
|
140
166
|
|
141
|
-
|
167
|
+
When you initialize SpecForge, it creates a `forge_helper.rb` file in your `spec_forge` directory. This serves as your central configuration file:
|
142
168
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
169
|
+
```ruby
|
170
|
+
SpecForge.configure do |config|
|
171
|
+
# Base URL for all requests
|
172
|
+
config.base_url = "http://localhost:3000"
|
173
|
+
|
174
|
+
# Default headers sent with every request
|
175
|
+
config.headers = {
|
176
|
+
"Authorization" => "Bearer #{ENV.fetch("API_TOKEN", "")}",
|
177
|
+
"Accept" => "application/json"
|
178
|
+
}
|
179
|
+
|
180
|
+
# Optional: Default query parameters for all requests
|
181
|
+
config.query = {
|
182
|
+
api_key: ENV["API_KEY"]
|
183
|
+
}
|
184
|
+
end
|
185
|
+
```
|
147
186
|
|
148
|
-
|
149
|
-
# config.yml
|
150
|
-
base_url: https://api.example.com
|
187
|
+
### Framework Integration
|
151
188
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
189
|
+
SpecForge works seamlessly with Rails and RSpec:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
# Rails Integration
|
193
|
+
require_relative "../config/environment"
|
194
|
+
|
195
|
+
# RSpec Integration (includes your existing configurations)
|
196
|
+
require_relative "../spec/spec_helper"
|
197
|
+
|
198
|
+
# Load custom files (models, libraries, etc)
|
199
|
+
Dir[File.join(__dir__, "..", "lib", "**", "*.rb")].sort.each { |f| require f }
|
200
|
+
```
|
201
|
+
|
202
|
+
### Factory Configuration
|
203
|
+
|
204
|
+
SpecForge provides flexible configuration options for working with FactoryBot factories:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
SpecForge.configure do |config|
|
208
|
+
# Disable auto-discovery if needed (default: true)
|
209
|
+
config.factories.auto_discover = false
|
210
|
+
|
211
|
+
# Add custom factory paths (appends to default paths)
|
212
|
+
config.factories.paths += ["lib/factories"]
|
213
|
+
end
|
214
|
+
```
|
215
|
+
|
216
|
+
### Debug Configuration
|
217
|
+
|
218
|
+
Enable debugging by adding `debug: true` (aliases: `breakpoint`, `pry`) at either the spec or expectation level:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
SpecForge.configure do |config|
|
222
|
+
# Custom debug handler (defaults to printing state overview)
|
223
|
+
config.on_debug { binding.pry } # Requires 'pry' gem
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
```yaml
|
228
|
+
get_users:
|
229
|
+
debug: true # Debug all expectations in this spec
|
230
|
+
path: /users
|
156
231
|
expectations:
|
157
|
-
-
|
158
|
-
base_url: https://prod.example.com # Overrides spec level
|
159
|
-
expect:
|
232
|
+
- expect:
|
160
233
|
status: 200
|
234
|
+
- debug: true # Debug just this expectation
|
235
|
+
expect:
|
236
|
+
status: 404
|
237
|
+
json:
|
238
|
+
error: kind_of.string
|
239
|
+
```
|
240
|
+
|
241
|
+
When debugging, you have access to:
|
242
|
+
- `expectation` - Current expectation being validated
|
243
|
+
- `variables` - Resolved variables for the current expectation
|
244
|
+
- `request` - Request details (url, method, headers, etc.)
|
245
|
+
- `response` - Full response including headers, status, and parsed body
|
246
|
+
- `expected_status` - Expected HTTP status code
|
247
|
+
- `expected_json` - Expected JSON structure with matchers
|
248
|
+
|
249
|
+
Or call `self` from an interactive session to see everything as a hash
|
250
|
+
|
251
|
+
### Test Framework Configuration
|
252
|
+
|
253
|
+
Access RSpec's configuration through the `specs` attribute:
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
SpecForge.configure do |config|
|
257
|
+
# Setup before all tests
|
258
|
+
config.specs.before(:suite) do
|
259
|
+
DatabaseCleaner.strategy = :truncation
|
260
|
+
DatabaseCleaner.clean_with(:truncation)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Wrap each test
|
264
|
+
config.specs.around do |example|
|
265
|
+
DatabaseCleaner.cleaning do
|
266
|
+
example.run
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
161
270
|
```
|
162
271
|
|
163
|
-
###
|
272
|
+
### Configuration Inheritance
|
164
273
|
|
165
|
-
|
274
|
+
All configuration options can be overridden at three levels (in order of precedence):
|
275
|
+
|
276
|
+
1. Individual expectation
|
277
|
+
2. Spec level
|
278
|
+
3. Global configuration (forge_helper.rb)
|
279
|
+
|
280
|
+
For example:
|
166
281
|
|
167
282
|
```yaml
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
283
|
+
# Override at spec level
|
284
|
+
get_user:
|
285
|
+
base_url: https://staging.example.com
|
286
|
+
headers:
|
287
|
+
x_custom_header: "overridden" # Underscore keys automatically convert to "X-Custom-Header"
|
288
|
+
|
289
|
+
expectations:
|
290
|
+
# Override for a specific expectation
|
291
|
+
- base_url: https://prod.example.com
|
292
|
+
headers:
|
293
|
+
X-Custom-Header: "expectation-specific" # HTTP-style headers used as-is
|
294
|
+
expect:
|
295
|
+
status: 200
|
172
296
|
```
|
173
297
|
|
174
|
-
##
|
298
|
+
## Writing Tests
|
175
299
|
|
176
300
|
### Basic Structure
|
177
301
|
|
178
|
-
Every spec needs a path, HTTP method, and at least one expectation
|
302
|
+
Every spec needs a path, HTTP method, and at least one expectation:
|
179
303
|
|
180
304
|
```yaml
|
181
305
|
show_user:
|
182
306
|
path: /users/1
|
183
|
-
method: GET
|
307
|
+
method: GET # Optional for GET requests
|
184
308
|
expectations:
|
185
|
-
-
|
186
|
-
expect:
|
309
|
+
- expect:
|
187
310
|
status: 200
|
188
311
|
```
|
189
312
|
|
190
313
|
### Testing Response Data
|
191
314
|
|
192
|
-
|
315
|
+
Verify the response JSON:
|
193
316
|
|
194
317
|
```yaml
|
195
318
|
show_user:
|
196
319
|
path: /users/1
|
197
|
-
method: GET
|
198
320
|
expectations:
|
199
|
-
-
|
200
|
-
expect:
|
321
|
+
- expect:
|
201
322
|
status: 200
|
202
323
|
json:
|
203
324
|
id: 1
|
@@ -207,58 +328,62 @@ show_user:
|
|
207
328
|
|
208
329
|
### Multiple Expectations
|
209
330
|
|
210
|
-
Each expectation can override any spec-level setting
|
331
|
+
Each expectation can override any spec-level setting:
|
211
332
|
|
212
333
|
```yaml
|
213
334
|
show_user:
|
214
335
|
path: /users/1
|
215
|
-
method: GET
|
216
336
|
expectations:
|
217
|
-
-
|
218
|
-
expect:
|
337
|
+
- expect:
|
219
338
|
status: 200
|
220
339
|
json:
|
221
340
|
id: 1
|
222
341
|
role: admin
|
223
|
-
-
|
224
|
-
path: /users/999 # Overrides spec-level path
|
342
|
+
- path: /users/999 # Overrides spec-level path
|
225
343
|
expect:
|
226
344
|
status: 404
|
227
345
|
```
|
228
346
|
|
229
347
|
### Request Data
|
230
348
|
|
231
|
-
|
349
|
+
Add query parameters and body data:
|
232
350
|
|
233
351
|
```yaml
|
234
352
|
create_user:
|
235
353
|
path: /users
|
236
354
|
method: POST
|
237
|
-
query: # or
|
355
|
+
query: # or "params" if you prefer
|
238
356
|
team_id: 123
|
239
|
-
body: # or
|
357
|
+
body: # or "data" if you prefer
|
240
358
|
name: John Doe
|
241
359
|
email: john@example.com
|
242
|
-
role: admin
|
243
360
|
expectations:
|
244
361
|
- expect:
|
245
362
|
status: 201
|
246
|
-
json:
|
247
|
-
id: kind_of.integer
|
248
|
-
name: John Doe
|
249
363
|
```
|
250
364
|
|
251
|
-
|
365
|
+
### Path Parameters
|
252
366
|
|
253
|
-
|
367
|
+
Use placeholders for dynamic path parameters:
|
368
|
+
|
369
|
+
```yaml
|
370
|
+
show_user:
|
371
|
+
path: /users/{id} # Use {id} or :id
|
372
|
+
query:
|
373
|
+
id: 1 # Replaces the placeholder
|
374
|
+
expectations:
|
375
|
+
- expect:
|
376
|
+
status: 200
|
377
|
+
```
|
378
|
+
|
379
|
+
## Dynamic Features
|
254
380
|
|
255
381
|
### Variables
|
256
382
|
|
257
|
-
Variables let you define and reuse values
|
383
|
+
Variables let you define and reuse values:
|
258
384
|
|
259
385
|
```yaml
|
260
386
|
list_posts:
|
261
|
-
path: /posts
|
262
387
|
variables:
|
263
388
|
author: factories.user
|
264
389
|
category_name: faker.lorem.word
|
@@ -266,24 +391,15 @@ list_posts:
|
|
266
391
|
author_id: variables.author.id
|
267
392
|
category: variables.category_name
|
268
393
|
expectations:
|
269
|
-
-
|
270
|
-
expect:
|
394
|
+
- expect:
|
271
395
|
status: 200
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
```
|
280
|
-
|
281
|
-
Variables support deep traversal:
|
282
|
-
```yaml
|
283
|
-
variables:
|
284
|
-
user: factories.user
|
285
|
-
first_post: variables.user.posts.last
|
286
|
-
author: variables.first_post.comments.2.author.name
|
396
|
+
json:
|
397
|
+
posts:
|
398
|
+
matcher.include:
|
399
|
+
- author:
|
400
|
+
id: variables.author.id
|
401
|
+
name: variables.author.name
|
402
|
+
category: variables.category_name
|
287
403
|
```
|
288
404
|
|
289
405
|
### Transformations
|
@@ -305,47 +421,118 @@ create_user:
|
|
305
421
|
email: faker.internet.email
|
306
422
|
```
|
307
423
|
|
308
|
-
###
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
424
|
+
### Chaining
|
425
|
+
|
426
|
+
Access nested attributes and methods through chaining:
|
427
|
+
|
428
|
+
```yaml
|
429
|
+
list_posts:
|
430
|
+
variables:
|
431
|
+
# Factory chaining examples
|
432
|
+
owner: factories.user # Creates a user
|
433
|
+
name: factories.user.name # Gets just the name
|
434
|
+
company: variables.owner.company # Access factory attributes
|
435
|
+
|
436
|
+
# Variable chaining for relationships
|
437
|
+
first_post: variables.user.posts.first
|
438
|
+
|
439
|
+
# You can use array indices directly
|
440
|
+
comment_author: variables.first_post.comments.2.author.name
|
441
|
+
|
442
|
+
# Faker method chaining
|
443
|
+
lowercase_email: faker.internet.email.downcase
|
444
|
+
title_name: faker.name.first_name.titleize
|
445
|
+
```
|
446
|
+
|
447
|
+
### Factory Build Strategies
|
448
|
+
|
449
|
+
Control how factories create objects and customize their attributes:
|
450
|
+
|
451
|
+
```yaml
|
452
|
+
create_user:
|
453
|
+
variables:
|
454
|
+
# Default strategy (create)
|
455
|
+
regular_user: factories.user
|
456
|
+
|
457
|
+
# Custom build strategy and attributes
|
458
|
+
custom_user:
|
459
|
+
factory.user:
|
460
|
+
strategy: build # 'create' (default) or 'build'
|
461
|
+
attributes:
|
462
|
+
name: "Custom Name"
|
463
|
+
email: faker.internet.email
|
464
|
+
```
|
465
|
+
|
466
|
+
## Factory Support
|
467
|
+
|
468
|
+
### Automatic Discovery
|
469
|
+
|
470
|
+
SpecForge automatically discovers factories in standard paths:
|
471
|
+
|
472
|
+
```ruby
|
473
|
+
SpecForge.configure do |config|
|
474
|
+
# Disable automatic factory discovery if needed (default: true)
|
475
|
+
config.factories.auto_discover = false
|
476
|
+
end
|
477
|
+
```
|
478
|
+
|
479
|
+
### Custom Factory Paths
|
480
|
+
|
481
|
+
Add custom paths to the factory search list:
|
482
|
+
|
483
|
+
```ruby
|
484
|
+
SpecForge.configure do |config|
|
485
|
+
# Add custom factory paths (appends to default paths)
|
486
|
+
# Ignored if `auto_discovery` is false
|
487
|
+
config.factories.paths += ["lib/factories"]
|
488
|
+
end
|
489
|
+
```
|
490
|
+
|
491
|
+
### Factory Build Strategies
|
492
|
+
|
493
|
+
Control how factories create objects and customize their attributes:
|
494
|
+
|
338
495
|
```yaml
|
339
|
-
|
496
|
+
create_user:
|
340
497
|
variables:
|
341
|
-
|
498
|
+
# Default strategy (create)
|
499
|
+
regular_user: factories.user
|
500
|
+
|
501
|
+
# Custom build strategy and attributes
|
502
|
+
custom_user:
|
503
|
+
factory.user:
|
504
|
+
strategy: build # 'create' (default) or 'build'
|
505
|
+
attributes:
|
506
|
+
name: "Custom Name"
|
507
|
+
email: faker.internet.email
|
508
|
+
```
|
509
|
+
|
510
|
+
### YAML Factory Definitions
|
511
|
+
|
512
|
+
Define factories in YAML with a simple declarative syntax:
|
513
|
+
|
514
|
+
```yaml
|
515
|
+
# spec_forge/factories/user.yml
|
516
|
+
user:
|
517
|
+
class: User # Optional model class name
|
518
|
+
variables:
|
519
|
+
department: faker.company.department
|
520
|
+
team_size:
|
521
|
+
faker.number.between:
|
522
|
+
from: 5
|
523
|
+
to: 20
|
524
|
+
attributes:
|
525
|
+
name: faker.name.name
|
526
|
+
email: faker.internet.email
|
527
|
+
role: admin
|
528
|
+
department: variables.department
|
529
|
+
team_count: variables.team_size
|
342
530
|
```
|
343
531
|
|
344
532
|
## RSpec Matchers
|
345
533
|
|
346
|
-
|
534
|
+
### "be" namespace
|
347
535
|
|
348
|
-
#### "be" namespace
|
349
536
|
```yaml
|
350
537
|
expect:
|
351
538
|
json:
|
@@ -356,22 +543,23 @@ expect:
|
|
356
543
|
tags: be.empty
|
357
544
|
email: be.present
|
358
545
|
|
359
|
-
# Comparisons
|
546
|
+
# Comparisons
|
360
547
|
price:
|
361
|
-
be.greater_than: 18
|
548
|
+
be.greater_than: 18
|
362
549
|
stock:
|
363
|
-
be.less_than_or_equal: 100
|
550
|
+
be.less_than_or_equal: 100
|
364
551
|
rating:
|
365
552
|
be.between:
|
366
553
|
- 1
|
367
554
|
- 5
|
368
555
|
|
369
556
|
# Dynamic predicate methods
|
370
|
-
published: be.published
|
371
|
-
admin: be.admin
|
557
|
+
published: be.published
|
558
|
+
admin: be.admin
|
372
559
|
```
|
373
560
|
|
374
|
-
|
561
|
+
### "kind_of" namespace
|
562
|
+
|
375
563
|
```yaml
|
376
564
|
expect:
|
377
565
|
json:
|
@@ -381,11 +569,11 @@ expect:
|
|
381
569
|
scores: kind_of.array
|
382
570
|
```
|
383
571
|
|
384
|
-
|
572
|
+
### "matchers" namespace
|
573
|
+
|
385
574
|
```yaml
|
386
575
|
expect:
|
387
576
|
json:
|
388
|
-
# Direct RSpec matcher usage
|
389
577
|
tags:
|
390
578
|
matcher.include:
|
391
579
|
- featured
|
@@ -393,19 +581,54 @@ expect:
|
|
393
581
|
|
394
582
|
slug: /^[a-z0-9-]+$/ # Shorthand for matching regexes
|
395
583
|
|
396
|
-
# Any RSpec matcher can be used
|
397
584
|
config:
|
398
585
|
matcher.have_key: api_version
|
399
586
|
```
|
400
587
|
|
401
|
-
|
588
|
+
## How Tests Work
|
402
589
|
|
403
|
-
|
590
|
+
When you write a YAML spec, SpecForge converts it into an RSpec test structure. For example, this YAML:
|
404
591
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
592
|
+
```yaml
|
593
|
+
create_user:
|
594
|
+
path: /users
|
595
|
+
method: POST
|
596
|
+
variables:
|
597
|
+
full_name: faker.name.name
|
598
|
+
body:
|
599
|
+
name: variables.full_name
|
600
|
+
expectations:
|
601
|
+
- expect:
|
602
|
+
status: 201
|
603
|
+
json:
|
604
|
+
name: variables.full_name
|
605
|
+
```
|
606
|
+
|
607
|
+
Becomes this RSpec test:
|
608
|
+
|
609
|
+
```ruby
|
610
|
+
RSpec.describe "create_user" do
|
611
|
+
describe "POST /users" do
|
612
|
+
let(:full_name) { Faker::Name.name }
|
613
|
+
|
614
|
+
let!(:expected_status) { 201 }
|
615
|
+
let!(:expected_json) do
|
616
|
+
{
|
617
|
+
name: eq(full_name)
|
618
|
+
}
|
619
|
+
end
|
620
|
+
|
621
|
+
subject(:response) do
|
622
|
+
post("/users", body: { name: full_name })
|
623
|
+
end
|
624
|
+
|
625
|
+
it do
|
626
|
+
expect(response.status).to eq(expected_status)
|
627
|
+
expect(response.body).to include(expected_json)
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
```
|
409
632
|
|
410
633
|
## Contributing
|
411
634
|
|