spec_forge 0.4.0 → 0.6.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/.standard.yml +4 -0
- data/CHANGELOG.md +145 -1
- data/README.md +49 -638
- data/flake.lock +3 -3
- data/flake.nix +8 -2
- data/lib/spec_forge/attribute/chainable.rb +208 -20
- data/lib/spec_forge/attribute/factory.rb +141 -12
- data/lib/spec_forge/attribute/faker.rb +64 -15
- data/lib/spec_forge/attribute/global.rb +96 -0
- data/lib/spec_forge/attribute/literal.rb +15 -2
- data/lib/spec_forge/attribute/matcher.rb +188 -13
- data/lib/spec_forge/attribute/parameterized.rb +45 -20
- data/lib/spec_forge/attribute/regex.rb +55 -5
- data/lib/spec_forge/attribute/resolvable.rb +48 -5
- data/lib/spec_forge/attribute/resolvable_array.rb +62 -4
- data/lib/spec_forge/attribute/resolvable_hash.rb +62 -4
- data/lib/spec_forge/attribute/store.rb +65 -0
- data/lib/spec_forge/attribute/transform.rb +33 -5
- data/lib/spec_forge/attribute/variable.rb +37 -6
- data/lib/spec_forge/attribute.rb +168 -66
- data/lib/spec_forge/backtrace_formatter.rb +26 -3
- data/lib/spec_forge/callbacks.rb +79 -0
- data/lib/spec_forge/cli/actions.rb +27 -0
- data/lib/spec_forge/cli/command.rb +78 -24
- data/lib/spec_forge/cli/init.rb +11 -1
- data/lib/spec_forge/cli/new.rb +54 -3
- data/lib/spec_forge/cli/run.rb +20 -0
- data/lib/spec_forge/cli.rb +16 -5
- data/lib/spec_forge/configuration.rb +94 -25
- data/lib/spec_forge/context/callbacks.rb +91 -0
- data/lib/spec_forge/context/global.rb +72 -0
- data/lib/spec_forge/context/store.rb +148 -0
- data/lib/spec_forge/context/variables.rb +91 -0
- data/lib/spec_forge/context.rb +36 -0
- data/lib/spec_forge/core_ext/rspec.rb +24 -4
- data/lib/spec_forge/error.rb +267 -113
- data/lib/spec_forge/factory.rb +33 -14
- data/lib/spec_forge/filter.rb +87 -0
- data/lib/spec_forge/forge.rb +170 -0
- data/lib/spec_forge/http/backend.rb +99 -29
- data/lib/spec_forge/http/client.rb +23 -13
- data/lib/spec_forge/http/request.rb +74 -62
- data/lib/spec_forge/http/verb.rb +79 -0
- data/lib/spec_forge/http.rb +105 -0
- data/lib/spec_forge/loader.rb +254 -0
- data/lib/spec_forge/matchers.rb +130 -0
- data/lib/spec_forge/normalizer/configuration.rb +24 -11
- data/lib/spec_forge/normalizer/constraint.rb +22 -9
- data/lib/spec_forge/normalizer/expectation.rb +31 -12
- data/lib/spec_forge/normalizer/factory.rb +24 -11
- data/lib/spec_forge/normalizer/factory_reference.rb +32 -13
- data/lib/spec_forge/normalizer/global_context.rb +88 -0
- data/lib/spec_forge/normalizer/spec.rb +39 -16
- data/lib/spec_forge/normalizer.rb +255 -41
- data/lib/spec_forge/runner/callbacks.rb +246 -0
- data/lib/spec_forge/runner/debug_proxy.rb +213 -0
- data/lib/spec_forge/runner/listener.rb +54 -0
- data/lib/spec_forge/runner/metadata.rb +58 -0
- data/lib/spec_forge/runner/state.rb +99 -0
- data/lib/spec_forge/runner.rb +133 -119
- data/lib/spec_forge/spec/expectation/constraint.rb +95 -20
- data/lib/spec_forge/spec/expectation.rb +43 -51
- data/lib/spec_forge/spec.rb +83 -96
- data/lib/spec_forge/type.rb +36 -4
- data/lib/spec_forge/version.rb +4 -1
- data/lib/spec_forge.rb +161 -76
- metadata +20 -5
- data/spec_forge/factories/user.yml +0 -4
- data/spec_forge/forge_helper.rb +0 -37
- data/spec_forge/specs/users.yml +0 -65
data/README.md
CHANGED
@@ -4,30 +4,50 @@
|
|
4
4
|

|
5
5
|
[](https://github.com/itsthedevman/spec_forge/actions/workflows/main.yml)
|
6
6
|
|
7
|
+
> Note: The code in this repository represents the latest development version with new features and improvements that are being prepared for future releases. For the current stable version, check out [v0.6.0](https://github.com/itsthedevman/spec_forge/releases/tag/v0.6.0) on GitHub releases.
|
8
|
+
|
7
9
|
Write API tests in YAML that read like documentation:
|
8
10
|
|
9
11
|
```yaml
|
10
|
-
|
11
|
-
path: /users/
|
12
|
+
show_user:
|
13
|
+
path: /users/{id}
|
14
|
+
variables:
|
15
|
+
expected_status: 200
|
16
|
+
user_id: 1
|
17
|
+
query:
|
18
|
+
id: variables.user_id
|
12
19
|
expectations:
|
13
20
|
- expect:
|
14
|
-
status:
|
21
|
+
status: variables.expected_status
|
15
22
|
json:
|
16
23
|
name: kind_of.string
|
17
|
-
email:
|
24
|
+
email:
|
25
|
+
matcher.and:
|
26
|
+
- kind_of.string
|
27
|
+
- /@/
|
18
28
|
```
|
19
29
|
|
20
30
|
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.
|
21
31
|
|
22
32
|
## Why SpecForge?
|
23
33
|
|
24
|
-
|
34
|
+
1. **Living Documentation**: Your tests should serve as clear, readable documentation of your API's behavior.
|
35
|
+
2. **Reduce Boilerplate**: Write tests without repetitive setup code and HTTP configuration.
|
36
|
+
3. **Quick Setup**: Start testing APIs in minutes instead of spending hours on test infrastructure.
|
37
|
+
4. **Gradual Adoption**: Use alongside your existing test suite, introducing it incrementally where it makes sense.
|
38
|
+
5. **Developer & QA Collaboration**: Create a testing format that everyone can understand and maintain, regardless of Ruby expertise.
|
25
39
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
40
|
+
## Key Features
|
41
|
+
|
42
|
+
- **YAML-Based Tests**: Write clear, declarative tests that read like documentation
|
43
|
+
- **RSpec Integration**: Leverage all the power of RSpec matchers and expectations
|
44
|
+
- **FactoryBot Integration**: Generate test data with FactoryBot integration
|
45
|
+
- **Faker Integration**: Create realistic test data with Faker
|
46
|
+
- **Variable System**: Define and reference variables for dynamic test data
|
47
|
+
- **Context Storage**: Store API responses and reference them in subsequent tests
|
48
|
+
- **Compound Matchers**: Combine multiple validations with `matcher.and` for precise expectations
|
49
|
+
- **Global Variables**: Define shared configuration at the file level
|
50
|
+
- **Callback System**: Hook into the test lifecycle using Ruby for setup, teardown, and much more!
|
31
51
|
|
32
52
|
## When Not to Use SpecForge
|
33
53
|
|
@@ -38,75 +58,6 @@ Consider alternatives when you need:
|
|
38
58
|
3. **Custom Response Validation**: For validation logic beyond what matchers provide.
|
39
59
|
4. **Complex Non-JSON Testing**: While SpecForge handles basic XML/HTML responses (coming soon), complex validation might need specialized tools.
|
40
60
|
|
41
|
-
## Roadmap
|
42
|
-
|
43
|
-
Current development priorities:
|
44
|
-
- [ ] Array support for `json` expectations
|
45
|
-
- [ ] Negated matchers: `matcher.not`
|
46
|
-
- [ ] `create_list/build_list` factory strategies
|
47
|
-
- [ ] `transform.map` support
|
48
|
-
- [ ] XML/HTML response handling
|
49
|
-
- [ ] OpenAPI generation from tests
|
50
|
-
- [x] Support for running individual specs
|
51
|
-
- [x] Improved error handling
|
52
|
-
|
53
|
-
Have a feature request? Open an issue on GitHub!
|
54
|
-
|
55
|
-
## Looking for a Software Engineer?
|
56
|
-
|
57
|
-
I'm currently looking for opportunities where I can tackle meaningful problems and help build reliable software while mentoring the next generation of developers. If you're looking for a senior engineer with full-stack Rails expertise and a passion for clean, maintainable code, let's talk!
|
58
|
-
|
59
|
-
[bryan@itsthedevman.com](mailto:bryan@itsthedevman.com)
|
60
|
-
|
61
|
-
## Table of Contents
|
62
|
-
|
63
|
-
- [Compatibility](#compatibility)
|
64
|
-
- [Installation](#installation)
|
65
|
-
- [Getting Started](#getting-started)
|
66
|
-
- [Forging Your First Test](#forging-your-first-test)
|
67
|
-
- [Running Tests](#running-tests)
|
68
|
-
- [Targeting Specific Files](#targeting-specific-files)
|
69
|
-
- [Targeting Specific Specs](#targeting-specific-specs)
|
70
|
-
- [Targeting Individual Expectations](#targeting-individual-expectations)
|
71
|
-
- [Configuration](#configuration)
|
72
|
-
- [Basic Configuration](#basic-configuration)
|
73
|
-
- [Framework Integration](#framework-integration)
|
74
|
-
- [Factory Configuration](#factory-configuration)
|
75
|
-
- [Debug Configuration](#debug-configuration)
|
76
|
-
- [Test Framework Configuration](#test-framework-configuration)
|
77
|
-
- [Configuration Inheritance](#configuration-inheritance)
|
78
|
-
- [Writing Tests](#writing-tests)
|
79
|
-
- [Basic Structure](#basic-structure)
|
80
|
-
- [Testing Response Data](#testing-response-data)
|
81
|
-
- [Multiple Expectations](#multiple-expectations)
|
82
|
-
- [Request Data](#request-data)
|
83
|
-
- [Path Parameters](#path-parameters)
|
84
|
-
- [Dynamic Features](#dynamic-features)
|
85
|
-
- [Variables](#variables)
|
86
|
-
- [Transformations](#transformations)
|
87
|
-
- [Chaining Support](#chaining-support)
|
88
|
-
- [Factory Support](#factory-support)
|
89
|
-
- [Automatic Discovery](#automatic-discovery)
|
90
|
-
- [Custom Factory Paths](#custom-factory-paths)
|
91
|
-
- [Build Strategies](#build-strategies)
|
92
|
-
- [YAML Factory Definitions](#yaml-factory-definitions)
|
93
|
-
- [RSpec Matchers](#rspec-matchers)
|
94
|
-
- ["be" namespace](#be-namespace)
|
95
|
-
- ["kind_of" namespace](#kind_of-namespace)
|
96
|
-
- ["matchers" namespace](#matchers-namespace)
|
97
|
-
- [How Tests Work](#how-tests-work)
|
98
|
-
- [Contributing](#contributing)
|
99
|
-
- [License](#license)
|
100
|
-
- [Credits](#credits)
|
101
|
-
|
102
|
-
Also see: [API Documentation](https://itsthedevman.com/docs/spec_forge)
|
103
|
-
|
104
|
-
## Compatibility
|
105
|
-
|
106
|
-
Currently tested on:
|
107
|
-
- MRI Ruby 3.2+
|
108
|
-
- NixOS (see `flake.nix` for details)
|
109
|
-
|
110
61
|
## Installation
|
111
62
|
|
112
63
|
Add this line to your application's Gemfile:
|
@@ -135,587 +86,47 @@ Initialize the required directory structure:
|
|
135
86
|
spec_forge init
|
136
87
|
```
|
137
88
|
|
138
|
-
|
139
|
-
```bash
|
140
|
-
bundle exec spec_forge init
|
141
|
-
```
|
142
|
-
|
143
|
-
This creates the `spec_forge` directory containing factory definitions, test specifications, and global configuration.
|
144
|
-
|
145
|
-
## Forging Your First Test
|
146
|
-
|
147
|
-
Let's write a simple test to verify a user endpoint. Create a new spec file:
|
89
|
+
Create your first test:
|
148
90
|
|
149
91
|
```bash
|
150
92
|
spec_forge new spec users
|
151
93
|
```
|
152
94
|
|
153
|
-
|
154
|
-
|
155
|
-
```yaml
|
156
|
-
get_user:
|
157
|
-
path: /users/1
|
158
|
-
method: GET
|
159
|
-
expectations:
|
160
|
-
- expect:
|
161
|
-
status: 200
|
162
|
-
json:
|
163
|
-
id: 1
|
164
|
-
name: kind_of.string
|
165
|
-
email: /@/
|
166
|
-
```
|
167
|
-
|
168
|
-
Run your tests with:
|
95
|
+
Run your tests:
|
169
96
|
|
170
97
|
```bash
|
171
98
|
spec_forge run
|
172
99
|
```
|
173
100
|
|
174
|
-
|
175
|
-
|
176
|
-
```bash
|
177
|
-
spec_forge
|
178
|
-
```
|
179
|
-
|
180
|
-
## Running Tests
|
181
|
-
|
182
|
-
As your test suite grows, you'll want more control over which tests to run.
|
101
|
+
## Documentation
|
183
102
|
|
184
|
-
|
185
|
-
|
186
|
-
When working on a specific feature, run tests from a single file:
|
187
|
-
|
188
|
-
```bash
|
189
|
-
spec_forge users # Runs all tests in specs/users.yml
|
190
|
-
```
|
103
|
+
For comprehensive documentation, visit the [SpecForge Wiki](https://github.com/itsthedevman/spec_forge/wiki) which includes:
|
191
104
|
|
192
|
-
|
105
|
+
- [Getting Started Guide](https://github.com/itsthedevman/spec_forge/wiki/Getting-Started)
|
106
|
+
- [Configuration Options](https://github.com/itsthedevman/spec_forge/wiki/Configuration)
|
107
|
+
- [Writing Tests](https://github.com/itsthedevman/spec_forge/wiki/Writing-Tests)
|
108
|
+
- [Running Tests](https://github.com/itsthedevman/spec_forge/wiki/Running-Tests)
|
109
|
+
- [Debugging Guide](https://github.com/itsthedevman/spec_forge/wiki/Debugging)
|
110
|
+
- [Dynamic Features](https://github.com/itsthedevman/spec_forge/wiki/Dynamic-Features)
|
111
|
+
- [Factory Support](https://github.com/itsthedevman/spec_forge/wiki/Factory-Support)
|
112
|
+
- [RSpec Matchers](https://github.com/itsthedevman/spec_forge/wiki/RSpec-Matchers)
|
193
113
|
|
194
|
-
|
114
|
+
Also see the [API Documentation](https://itsthedevman.com/docs/spec_forge).
|
195
115
|
|
196
|
-
|
197
|
-
spec_forge users:destroy_user # Runs all expectations in the destroy_user spec
|
198
|
-
```
|
199
|
-
|
200
|
-
#### Targeting Individual Expectations
|
201
|
-
|
202
|
-
You can also run individual expectations within a spec. The format depends on whether the expectation has a name:
|
203
|
-
|
204
|
-
```yaml
|
205
|
-
# specs/users.yml
|
206
|
-
destroy_user:
|
207
|
-
path: /users/:id
|
208
|
-
method: delete
|
209
|
-
expectations:
|
210
|
-
- expect: # Unnamed expectation
|
211
|
-
status: 200
|
212
|
-
- name: "Destroys a User" # Named expectation
|
213
|
-
expect:
|
214
|
-
status: 200
|
215
|
-
```
|
216
|
-
|
217
|
-
For named expectations:
|
218
|
-
```bash
|
219
|
-
# Format: <file>:<spec>:'<verb> <path> - <name>'
|
220
|
-
spec_forge users:destroy_user:'DELETE /users/:id - Destroys a User'
|
221
|
-
```
|
222
|
-
|
223
|
-
For unnamed expectations:
|
224
|
-
```bash
|
225
|
-
# Format: <file>:<spec>:'<verb> <path>'
|
226
|
-
spec_forge users:destroy_user:'DELETE /users/:id'
|
227
|
-
```
|
228
|
-
|
229
|
-
**Note**: When targeting an unnamed expectation, SpecForge executes all matching expectations within that spec. This means if you have multiple unnamed expectations with the same verb and path, they will all run.
|
230
|
-
|
231
|
-
## Configuration
|
232
|
-
|
233
|
-
### Basic Configuration
|
234
|
-
|
235
|
-
When you initialize SpecForge, it creates a `forge_helper.rb` file in your `spec_forge` directory. This serves as your central configuration file:
|
236
|
-
|
237
|
-
```ruby
|
238
|
-
SpecForge.configure do |config|
|
239
|
-
# Base URL for all requests
|
240
|
-
config.base_url = "http://localhost:3000"
|
241
|
-
|
242
|
-
# Default headers sent with every request
|
243
|
-
config.headers = {
|
244
|
-
"Authorization" => "Bearer #{ENV.fetch("API_TOKEN", "")}",
|
245
|
-
"Accept" => "application/json"
|
246
|
-
}
|
247
|
-
|
248
|
-
# Optional: Default query parameters for all requests
|
249
|
-
config.query = {
|
250
|
-
api_key: ENV["API_KEY"]
|
251
|
-
}
|
252
|
-
end
|
253
|
-
```
|
116
|
+
## Future Development
|
254
117
|
|
255
|
-
|
256
|
-
|
257
|
-
SpecForge works seamlessly with Rails and RSpec:
|
258
|
-
|
259
|
-
```ruby
|
260
|
-
# Rails Integration
|
261
|
-
require_relative "../config/environment"
|
262
|
-
|
263
|
-
# RSpec Integration (includes your existing configurations)
|
264
|
-
require_relative "../spec/spec_helper"
|
265
|
-
|
266
|
-
# Load custom files (models, libraries, etc)
|
267
|
-
Dir[File.join(__dir__, "..", "lib", "**", "*.rb")].sort.each { |f| require f }
|
268
|
-
```
|
269
|
-
|
270
|
-
### Factory Configuration
|
271
|
-
|
272
|
-
SpecForge provides flexible configuration options for working with FactoryBot factories:
|
273
|
-
|
274
|
-
```ruby
|
275
|
-
SpecForge.configure do |config|
|
276
|
-
# Disable auto-discovery if needed (default: true)
|
277
|
-
config.factories.auto_discover = false
|
278
|
-
|
279
|
-
# Add custom factory paths (appends to default paths)
|
280
|
-
config.factories.paths += ["lib/factories"]
|
281
|
-
end
|
282
|
-
```
|
283
|
-
|
284
|
-
### Debug Configuration
|
285
|
-
|
286
|
-
Enable debugging by adding `debug: true` (aliases: `breakpoint`, `pry`) at either the spec or expectation level:
|
287
|
-
|
288
|
-
```ruby
|
289
|
-
SpecForge.configure do |config|
|
290
|
-
# Custom debug handler (defaults to printing state overview)
|
291
|
-
config.on_debug { binding.pry } # Requires 'pry' gem
|
292
|
-
end
|
293
|
-
```
|
294
|
-
|
295
|
-
```yaml
|
296
|
-
get_users:
|
297
|
-
debug: true # Debug all expectations in this spec
|
298
|
-
path: /users
|
299
|
-
expectations:
|
300
|
-
- expect:
|
301
|
-
status: 200
|
302
|
-
- debug: true # Debug just this expectation
|
303
|
-
expect:
|
304
|
-
status: 404
|
305
|
-
json:
|
306
|
-
error: kind_of.string
|
307
|
-
```
|
308
|
-
|
309
|
-
When debugging, you have access to:
|
310
|
-
- `expectation` - Current expectation being validated
|
311
|
-
- `variables` - Resolved variables for the current expectation
|
312
|
-
- `request` - Request details (url, method, headers, etc.)
|
313
|
-
- `response` - Full response including headers, status, and parsed body
|
314
|
-
- `expected_status` - Expected HTTP status code
|
315
|
-
- `expected_json` - Expected JSON structure with matchers
|
316
|
-
|
317
|
-
Or call `self` from an interactive session to see everything as a hash
|
318
|
-
|
319
|
-
### Test Framework Configuration
|
320
|
-
|
321
|
-
Access RSpec's configuration through the `specs` attribute:
|
322
|
-
|
323
|
-
```ruby
|
324
|
-
SpecForge.configure do |config|
|
325
|
-
# Setup before all tests
|
326
|
-
config.specs.before(:suite) do
|
327
|
-
DatabaseCleaner.strategy = :truncation
|
328
|
-
DatabaseCleaner.clean_with(:truncation)
|
329
|
-
end
|
330
|
-
|
331
|
-
# Wrap each test
|
332
|
-
config.specs.around do |example|
|
333
|
-
DatabaseCleaner.cleaning do
|
334
|
-
example.run
|
335
|
-
end
|
336
|
-
end
|
337
|
-
end
|
338
|
-
```
|
339
|
-
|
340
|
-
### Configuration Inheritance
|
341
|
-
|
342
|
-
All configuration options can be overridden at three levels (in order of precedence):
|
343
|
-
|
344
|
-
1. Individual expectation
|
345
|
-
2. Spec level
|
346
|
-
3. Global configuration (forge_helper.rb)
|
347
|
-
|
348
|
-
For example:
|
349
|
-
|
350
|
-
```yaml
|
351
|
-
# Override at spec level
|
352
|
-
get_user:
|
353
|
-
base_url: https://staging.example.com
|
354
|
-
headers:
|
355
|
-
x_custom_header: "overridden" # Underscore keys automatically convert to "X-Custom-Header"
|
356
|
-
|
357
|
-
expectations:
|
358
|
-
# Override for a specific expectation
|
359
|
-
- base_url: https://prod.example.com
|
360
|
-
headers:
|
361
|
-
X-Custom-Header: "expectation-specific" # HTTP-style headers used as-is
|
362
|
-
expect:
|
363
|
-
status: 200
|
364
|
-
```
|
365
|
-
|
366
|
-
## Writing Tests
|
367
|
-
|
368
|
-
### Basic Structure
|
369
|
-
|
370
|
-
Every spec needs a path, HTTP method, and at least one expectation:
|
371
|
-
|
372
|
-
```yaml
|
373
|
-
show_user:
|
374
|
-
path: /users/1
|
375
|
-
method: GET # Optional for GET requests
|
376
|
-
expectations:
|
377
|
-
- expect:
|
378
|
-
status: 200
|
379
|
-
```
|
380
|
-
|
381
|
-
### Testing Response Data
|
382
|
-
|
383
|
-
Verify the response JSON:
|
384
|
-
|
385
|
-
```yaml
|
386
|
-
show_user:
|
387
|
-
path: /users/1
|
388
|
-
expectations:
|
389
|
-
- expect:
|
390
|
-
status: 200
|
391
|
-
json:
|
392
|
-
id: 1
|
393
|
-
name: kind_of.string
|
394
|
-
role: admin
|
395
|
-
```
|
396
|
-
|
397
|
-
### Multiple Expectations
|
398
|
-
|
399
|
-
Each expectation can override any spec-level setting:
|
400
|
-
|
401
|
-
```yaml
|
402
|
-
show_user:
|
403
|
-
path: /users/1
|
404
|
-
expectations:
|
405
|
-
- expect:
|
406
|
-
status: 200
|
407
|
-
json:
|
408
|
-
id: 1
|
409
|
-
role: admin
|
410
|
-
- path: /users/999 # Overrides spec-level path
|
411
|
-
expect:
|
412
|
-
status: 404
|
413
|
-
```
|
414
|
-
|
415
|
-
### Request Data
|
416
|
-
|
417
|
-
Add query parameters and body data:
|
418
|
-
|
419
|
-
```yaml
|
420
|
-
create_user:
|
421
|
-
path: /users
|
422
|
-
method: POST
|
423
|
-
query: # or "params" if you prefer
|
424
|
-
team_id: 123
|
425
|
-
body: # or "data" if you prefer
|
426
|
-
name: John Doe
|
427
|
-
email: john@example.com
|
428
|
-
expectations:
|
429
|
-
- expect:
|
430
|
-
status: 201
|
431
|
-
```
|
432
|
-
|
433
|
-
### Path Parameters
|
434
|
-
|
435
|
-
Use placeholders for dynamic path parameters:
|
436
|
-
|
437
|
-
```yaml
|
438
|
-
show_user:
|
439
|
-
path: /users/{id} # Use {id} or :id
|
440
|
-
query:
|
441
|
-
id: 1 # Replaces the placeholder
|
442
|
-
expectations:
|
443
|
-
- expect:
|
444
|
-
status: 200
|
445
|
-
```
|
446
|
-
|
447
|
-
## Dynamic Features
|
448
|
-
|
449
|
-
### Variables
|
450
|
-
|
451
|
-
Variables let you define and reuse values:
|
452
|
-
|
453
|
-
```yaml
|
454
|
-
list_posts:
|
455
|
-
variables:
|
456
|
-
author: factories.user
|
457
|
-
category_name: faker.lorem.word
|
458
|
-
query:
|
459
|
-
author_id: variables.author.id
|
460
|
-
category: variables.category_name
|
461
|
-
expectations:
|
462
|
-
- expect:
|
463
|
-
status: 200
|
464
|
-
json:
|
465
|
-
posts:
|
466
|
-
matcher.include:
|
467
|
-
- author:
|
468
|
-
id: variables.author.id
|
469
|
-
name: variables.author.name
|
470
|
-
category: variables.category_name
|
471
|
-
```
|
472
|
-
|
473
|
-
### Transformations
|
474
|
-
|
475
|
-
Transform data using built-in helpers:
|
476
|
-
|
477
|
-
```yaml
|
478
|
-
create_user:
|
479
|
-
variables:
|
480
|
-
first_name: faker.name.first_name
|
481
|
-
last_name: faker.name.last_name
|
482
|
-
full_name:
|
483
|
-
transform.join:
|
484
|
-
- variables.first_name
|
485
|
-
- " "
|
486
|
-
- variables.last_name
|
487
|
-
body:
|
488
|
-
name: variables.full_name
|
489
|
-
email: faker.internet.email
|
490
|
-
```
|
491
|
-
|
492
|
-
### Chaining
|
493
|
-
|
494
|
-
Access nested attributes and methods through chaining:
|
495
|
-
|
496
|
-
```yaml
|
497
|
-
list_posts:
|
498
|
-
variables:
|
499
|
-
# Factory chaining examples
|
500
|
-
owner: factories.user # Creates a user
|
501
|
-
name: factories.user.name # Gets just the name
|
502
|
-
company: variables.owner.company # Access factory attributes
|
503
|
-
|
504
|
-
# Variable chaining for relationships
|
505
|
-
first_post: variables.user.posts.first
|
506
|
-
|
507
|
-
# You can use array indices directly
|
508
|
-
comment_author: variables.first_post.comments.2.author.name
|
509
|
-
|
510
|
-
# Faker method chaining
|
511
|
-
lowercase_email: faker.internet.email.downcase
|
512
|
-
title_name: faker.name.first_name.titleize
|
513
|
-
```
|
514
|
-
|
515
|
-
### Factory Build Strategies
|
516
|
-
|
517
|
-
Control how factories create objects and customize their attributes:
|
518
|
-
|
519
|
-
```yaml
|
520
|
-
create_user:
|
521
|
-
variables:
|
522
|
-
# Default strategy (create)
|
523
|
-
regular_user: factories.user
|
524
|
-
|
525
|
-
# Custom build strategy and attributes
|
526
|
-
custom_user:
|
527
|
-
factory.user:
|
528
|
-
strategy: build # 'create' (default) or 'build'
|
529
|
-
attributes:
|
530
|
-
name: "Custom Name"
|
531
|
-
email: faker.internet.email
|
532
|
-
```
|
533
|
-
|
534
|
-
## Factory Support
|
535
|
-
|
536
|
-
### Automatic Discovery
|
537
|
-
|
538
|
-
SpecForge automatically discovers factories in standard paths:
|
539
|
-
|
540
|
-
```ruby
|
541
|
-
SpecForge.configure do |config|
|
542
|
-
# Disable automatic factory discovery if needed (default: true)
|
543
|
-
config.factories.auto_discover = false
|
544
|
-
end
|
545
|
-
```
|
546
|
-
|
547
|
-
### Custom Factory Paths
|
548
|
-
|
549
|
-
Add custom paths to the factory search list:
|
550
|
-
|
551
|
-
```ruby
|
552
|
-
SpecForge.configure do |config|
|
553
|
-
# Add custom factory paths (appends to default paths)
|
554
|
-
# Ignored if `auto_discovery` is false
|
555
|
-
config.factories.paths += ["lib/factories"]
|
556
|
-
end
|
557
|
-
```
|
558
|
-
|
559
|
-
### Factory Build Strategies
|
560
|
-
|
561
|
-
Control how factories create objects and customize their attributes:
|
562
|
-
|
563
|
-
```yaml
|
564
|
-
create_user:
|
565
|
-
variables:
|
566
|
-
# Default strategy (create)
|
567
|
-
regular_user: factories.user
|
568
|
-
|
569
|
-
# Custom build strategy and attributes
|
570
|
-
custom_user:
|
571
|
-
factory.user:
|
572
|
-
strategy: build # 'create' (default) or 'build'
|
573
|
-
attributes:
|
574
|
-
name: "Custom Name"
|
575
|
-
email: faker.internet.email
|
576
|
-
```
|
577
|
-
|
578
|
-
### YAML Factory Definitions
|
579
|
-
|
580
|
-
Define factories in YAML with a simple declarative syntax:
|
581
|
-
|
582
|
-
```yaml
|
583
|
-
# spec_forge/factories/user.yml
|
584
|
-
user:
|
585
|
-
class: User # Optional model class name
|
586
|
-
variables:
|
587
|
-
department: faker.company.department
|
588
|
-
team_size:
|
589
|
-
faker.number.between:
|
590
|
-
from: 5
|
591
|
-
to: 20
|
592
|
-
attributes:
|
593
|
-
name: faker.name.name
|
594
|
-
email: faker.internet.email
|
595
|
-
role: admin
|
596
|
-
department: variables.department
|
597
|
-
team_count: variables.team_size
|
598
|
-
```
|
599
|
-
|
600
|
-
## RSpec Matchers
|
601
|
-
|
602
|
-
### "be" namespace
|
603
|
-
|
604
|
-
```yaml
|
605
|
-
expect:
|
606
|
-
json:
|
607
|
-
# Simple predicates
|
608
|
-
active: be.true
|
609
|
-
deleted: be.false
|
610
|
-
description: be.nil
|
611
|
-
tags: be.empty
|
612
|
-
email: be.present
|
613
|
-
|
614
|
-
# Comparisons
|
615
|
-
price:
|
616
|
-
be.greater_than: 18
|
617
|
-
stock:
|
618
|
-
be.less_than_or_equal: 100
|
619
|
-
rating:
|
620
|
-
be.between:
|
621
|
-
- 1
|
622
|
-
- 5
|
623
|
-
|
624
|
-
# Dynamic predicate methods
|
625
|
-
published: be.published
|
626
|
-
admin: be.admin
|
627
|
-
```
|
628
|
-
|
629
|
-
### "kind_of" namespace
|
630
|
-
|
631
|
-
```yaml
|
632
|
-
expect:
|
633
|
-
json:
|
634
|
-
id: kind_of.integer
|
635
|
-
name: kind_of.string
|
636
|
-
metadata: kind_of.hash
|
637
|
-
scores: kind_of.array
|
638
|
-
```
|
639
|
-
|
640
|
-
### "matchers" namespace
|
641
|
-
|
642
|
-
```yaml
|
643
|
-
expect:
|
644
|
-
json:
|
645
|
-
tags:
|
646
|
-
matcher.include:
|
647
|
-
- featured
|
648
|
-
- published
|
649
|
-
|
650
|
-
slug: /^[a-z0-9-]+$/ # Shorthand for matching regexes
|
651
|
-
|
652
|
-
config:
|
653
|
-
matcher.have_key: api_version
|
654
|
-
```
|
655
|
-
|
656
|
-
## How Tests Work
|
657
|
-
|
658
|
-
When you write a YAML spec, SpecForge converts it into an RSpec test structure. For example, this YAML:
|
659
|
-
|
660
|
-
```yaml
|
661
|
-
create_user:
|
662
|
-
path: /users
|
663
|
-
method: POST
|
664
|
-
variables:
|
665
|
-
full_name: faker.name.name
|
666
|
-
body:
|
667
|
-
name: variables.full_name
|
668
|
-
expectations:
|
669
|
-
- expect:
|
670
|
-
status: 201
|
671
|
-
json:
|
672
|
-
name: variables.full_name
|
673
|
-
```
|
674
|
-
|
675
|
-
Becomes this RSpec test:
|
676
|
-
|
677
|
-
```ruby
|
678
|
-
RSpec.describe "create_user" do
|
679
|
-
describe "POST /users" do
|
680
|
-
let(:full_name) { Faker::Name.name }
|
681
|
-
|
682
|
-
let!(:expected_status) { 201 }
|
683
|
-
let!(:expected_json) do
|
684
|
-
{
|
685
|
-
name: eq(full_name)
|
686
|
-
}
|
687
|
-
end
|
688
|
-
|
689
|
-
subject(:response) do
|
690
|
-
post("/users", body: { name: full_name })
|
691
|
-
end
|
692
|
-
|
693
|
-
it do
|
694
|
-
expect(response.status).to eq(expected_status)
|
695
|
-
expect(response.body).to include(expected_json)
|
696
|
-
end
|
697
|
-
end
|
698
|
-
end
|
699
|
-
```
|
118
|
+
For the latest development priorities and feature ideas, check out our [Github Project](https://github.com/itsthedevman/spec_forge/projects?query=is%3Aopen). Have a feature request? Open an issue on GitHub!
|
700
119
|
|
701
120
|
## Contributing
|
702
121
|
|
703
|
-
|
704
|
-
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
|
705
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
706
|
-
4. Push to the branch (`git push origin feature/my-new-feature`)
|
707
|
-
5. Create new Pull Request
|
708
|
-
|
709
|
-
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
122
|
+
Contributions are welcome! See the [Contributing Guide](https://github.com/itsthedevman/spec_forge/wiki/Contributing) for details on how to get started.
|
710
123
|
|
711
124
|
## License
|
712
125
|
|
713
126
|
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
714
127
|
|
715
|
-
##
|
716
|
-
|
717
|
-
See [CHANGELOG.md](CHANGELOG.md) for a list of changes.
|
128
|
+
## Looking for a Software Engineer?
|
718
129
|
|
719
|
-
|
130
|
+
I'm currently looking for opportunities where I can tackle meaningful problems and help build reliable software while mentoring the next generation of developers. If you're looking for a senior engineer with full-stack Rails expertise and a passion for clean, maintainable code, let's talk!
|
720
131
|
|
721
|
-
|
132
|
+
[bryan@itsthedevman.com](mailto:bryan@itsthedevman.com)
|