rspec_in_context 1.1.0.3 → 1.2.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_and_publish.yml +15 -10
- data/.github/workflows/test_only.yml +12 -7
- data/.github/workflows/verify_version_change.yml +6 -2
- data/.gitignore +7 -0
- data/.rubocop-remote-fc7fff8f41d19bde0dcc446ded612e75.yml +153 -0
- data/.rubocop.yml +12 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +36 -1
- data/LLMS.md +388 -0
- data/README.md +237 -73
- data/TODO.md +44 -0
- data/examples/README.md +23 -0
- data/examples/contexts/active_job_context.rb +15 -0
- data/examples/contexts/authenticated_request_context.rb +27 -0
- data/examples/contexts/composed_context.rb +13 -0
- data/examples/contexts/frozen_time_context.rb +8 -0
- data/examples/contexts/inline_mailer_context.rb +15 -0
- data/examples/contexts/interactor_contract_context.rb +23 -0
- data/examples/usage/composed_context_spec.rb +15 -0
- data/examples/usage/interactor_spec.rb +23 -0
- data/examples/usage/nested_contexts_spec.rb +30 -0
- data/examples/usage/request_spec.rb +29 -0
- data/lib/rspec_in_context/context_management.rb +1 -3
- data/lib/rspec_in_context/in_context.rb +137 -35
- data/lib/rspec_in_context/version.rb +1 -3
- data/lib/rspec_in_context.rb +13 -9
- data/package.json +16 -2
- data/rspec_in_context.gemspec +21 -20
- data/yarn.lock +8 -10
- metadata +28 -59
data/README.md
CHANGED
|
@@ -1,12 +1,69 @@
|
|
|
1
1
|
# RspecInContext
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/rspec_in_context)
|
|
4
|
-
[](https://www.codacy.com/gh/zaratan/rspec_in_context/dashboard?utm_source=github.com&utm_medium=referral&utm_content=zaratan/rspec_in_context&utm_campaign=Badge_Grade)
|
|
5
4
|

|
|
6
5
|
|
|
7
|
-
This gem is here to help you write better shared_examples in
|
|
6
|
+
This gem is here to help you write better shared_examples in RSpec.
|
|
8
7
|
|
|
9
|
-
Ever been bothered by the fact that they don't really behave like methods and that you can't pass
|
|
8
|
+
Ever been bothered by the fact that they don't really behave like methods and that you can't pass them a block? There you go: `rspec_in_context`
|
|
9
|
+
|
|
10
|
+
## Why not just shared_examples?
|
|
11
|
+
|
|
12
|
+
`shared_examples` are great but they have a few limitations that can get annoying:
|
|
13
|
+
|
|
14
|
+
**You can't inject a block of tests at a specific point.** With `shared_examples`, your tests are either all inside the shared example or all outside. There's no way to say "set things up, run _these_ tests, then tear down". With `in_context`, you place `execute_tests` exactly where you want the caller's block to be injected.
|
|
15
|
+
|
|
16
|
+
**Composing them is awkward.** Nesting `it_behaves_like` inside another `shared_examples` works but reads poorly. `in_context` calls nest naturally, and you can use `in_context` inside a `define_context`.
|
|
17
|
+
|
|
18
|
+
**They don't accept arguments naturally.** `shared_examples` rely on `let` or params passed via `include_examples`. `in_context` accepts arguments directly, like a method call:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# shared_examples way
|
|
22
|
+
shared_examples "validates presence" do
|
|
23
|
+
it { is_expected.not_to be_valid }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
RSpec.describe User do
|
|
27
|
+
context "when email is nil" do
|
|
28
|
+
let(:email) { nil }
|
|
29
|
+
it_behaves_like "validates presence"
|
|
30
|
+
end
|
|
31
|
+
context "when name is nil" do
|
|
32
|
+
let(:name) { nil }
|
|
33
|
+
it_behaves_like "validates presence"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# in_context way
|
|
38
|
+
RSpec.define_context :validates_presence do |field|
|
|
39
|
+
context "when #{field} is nil" do
|
|
40
|
+
let(field) { nil }
|
|
41
|
+
it { is_expected.not_to be_valid }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
RSpec.describe User do
|
|
46
|
+
in_context :validates_presence, :email
|
|
47
|
+
in_context :validates_presence, :name
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
In short: `in_context` makes reusable test blocks behave more like methods.
|
|
52
|
+
|
|
53
|
+
## Table of Contents
|
|
54
|
+
|
|
55
|
+
- [Why not just shared_examples?](#why-not-just-shared_examples)
|
|
56
|
+
- [Installation](#installation)
|
|
57
|
+
- [Usage](#usage)
|
|
58
|
+
- [Add this into RSpec](#add-this-into-rspec)
|
|
59
|
+
- [Define a new in_context](#define-a-new-in_context)
|
|
60
|
+
- [Use the context](#use-the-context)
|
|
61
|
+
- [Things to know](#things-to-know)
|
|
62
|
+
- [Errors](#errors)
|
|
63
|
+
- [Examples](#examples)
|
|
64
|
+
- [Migrating to 1.2.0](#migrating-to-120)
|
|
65
|
+
- [Development](#development)
|
|
66
|
+
- [Contributing](#contributing)
|
|
10
67
|
|
|
11
68
|
## Installation
|
|
12
69
|
|
|
@@ -26,28 +83,28 @@ Or install it yourself as:
|
|
|
26
83
|
|
|
27
84
|
## Usage
|
|
28
85
|
|
|
29
|
-
### Add this into
|
|
86
|
+
### Add this into RSpec
|
|
30
87
|
|
|
31
88
|
You must require the gem on top of your spec_helper:
|
|
32
89
|
```ruby
|
|
33
90
|
require 'rspec_in_context'
|
|
34
91
|
```
|
|
35
92
|
|
|
36
|
-
Then include it into
|
|
93
|
+
Then include it into RSpec:
|
|
37
94
|
```ruby
|
|
38
95
|
RSpec.configure do |config|
|
|
39
96
|
[...]
|
|
40
|
-
|
|
97
|
+
|
|
41
98
|
config.include RspecInContext
|
|
42
99
|
end
|
|
43
100
|
```
|
|
44
101
|
|
|
45
102
|
### Define a new in_context
|
|
46
103
|
|
|
47
|
-
You can define in_context
|
|
48
|
-
They completely look like normal
|
|
104
|
+
You can define in_context blocks that are reusable almost anywhere.
|
|
105
|
+
They completely look like normal RSpec.
|
|
49
106
|
|
|
50
|
-
##### Inside a
|
|
107
|
+
##### Inside a RSpec block (scoped)
|
|
51
108
|
|
|
52
109
|
```ruby
|
|
53
110
|
# A in_context can be named with a symbol or a string
|
|
@@ -60,19 +117,36 @@ end
|
|
|
60
117
|
|
|
61
118
|
Those in_context will be scoped to their current `describe`/`context` block.
|
|
62
119
|
|
|
63
|
-
##### Outside a
|
|
120
|
+
##### Outside a RSpec block (globally)
|
|
64
121
|
|
|
65
122
|
Outside of a test you have to use `RSpec.define_context`. Those in_context will be defined globally in your tests.
|
|
66
123
|
|
|
124
|
+
##### File organization
|
|
125
|
+
|
|
126
|
+
For global contexts, we recommend creating a `spec/contexts/` directory with one file per context (or per group of related contexts):
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
spec/
|
|
130
|
+
contexts/
|
|
131
|
+
authenticated_request_context.rb
|
|
132
|
+
frozen_time_context.rb
|
|
133
|
+
interactor_contract_context.rb
|
|
134
|
+
spec_helper.rb
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Then require them in your `spec_helper.rb`:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
Dir[File.join(__dir__, "contexts", "**", "*.rb")].each { |f| require f }
|
|
141
|
+
```
|
|
67
142
|
|
|
68
143
|
### Use the context
|
|
69
144
|
|
|
70
145
|
Anywhere in your test description, use a `in_context` block to use a predefined in_context.
|
|
71
146
|
|
|
72
|
-
**Important**: in_context are scoped to their current `describe`/`context` block. If you need globally defined
|
|
147
|
+
**Important**: in_context are scoped to their current `describe`/`context` block. If you need globally defined contexts see `RSpec.define_context`
|
|
73
148
|
|
|
74
149
|
```ruby
|
|
75
|
-
# A in_context can be named with a symbol or a string
|
|
76
150
|
RSpec.define_context :context_name do
|
|
77
151
|
it 'works' do
|
|
78
152
|
expect(true).to be_truthy
|
|
@@ -80,7 +154,7 @@ RSpec.define_context :context_name do
|
|
|
80
154
|
end
|
|
81
155
|
|
|
82
156
|
[...]
|
|
83
|
-
|
|
157
|
+
RSpec.describe MyClass do
|
|
84
158
|
in_context :context_name # => will execute the 'it works' test here
|
|
85
159
|
end
|
|
86
160
|
```
|
|
@@ -89,94 +163,129 @@ end
|
|
|
89
163
|
|
|
90
164
|
#### Inside block execution
|
|
91
165
|
|
|
92
|
-
* You can
|
|
166
|
+
* You can choose exactly where your inside test will be used:
|
|
93
167
|
By using `execute_tests` in your define context, the test passed when you *use* the context will be executed here
|
|
94
168
|
|
|
95
169
|
```ruby
|
|
96
|
-
define_context :
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
170
|
+
RSpec.define_context :authenticated_request do
|
|
171
|
+
let(:user) { create(:user) }
|
|
172
|
+
|
|
173
|
+
before { sign_in user }
|
|
174
|
+
|
|
175
|
+
context "without authentication" do
|
|
176
|
+
before { sign_out user }
|
|
177
|
+
|
|
178
|
+
it "redirects to login" do
|
|
179
|
+
send(http_method, endpoint_path)
|
|
180
|
+
expect(response).to redirect_to(new_user_session_path)
|
|
181
|
+
end
|
|
104
182
|
end
|
|
183
|
+
|
|
184
|
+
execute_tests
|
|
105
185
|
end
|
|
106
186
|
|
|
107
187
|
[...]
|
|
108
188
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
189
|
+
RSpec.describe "Projects", type: :request do
|
|
190
|
+
let(:http_method) { :get }
|
|
191
|
+
let(:endpoint_path) { projects_path }
|
|
192
|
+
|
|
193
|
+
in_context :authenticated_request do
|
|
194
|
+
it "returns 200" do
|
|
195
|
+
get projects_path
|
|
196
|
+
expect(response).to have_http_status(:ok)
|
|
197
|
+
end
|
|
112
198
|
end
|
|
113
199
|
end
|
|
114
200
|
```
|
|
115
201
|
|
|
202
|
+
The block you pass to `in_context` gets injected exactly where `execute_tests` is placed. Setup, teardown, and built-in tests live together in the context definition. Your specific tests are injected right where they belong.
|
|
203
|
+
|
|
116
204
|
* You can add variable instantiation relative to your test where you exactly want:
|
|
117
205
|
|
|
118
|
-
`
|
|
119
|
-
But it
|
|
206
|
+
`instantiate_context` is an alias of `execute_tests` so you can't use both.
|
|
207
|
+
But it lets you describe what the block will do better.
|
|
208
|
+
|
|
209
|
+
> **Note**: The old spelling `instanciate_context` still works but is deprecated and will emit a warning.
|
|
120
210
|
|
|
121
211
|
#### Variable usage
|
|
122
212
|
|
|
123
|
-
* You can use
|
|
213
|
+
* You can use variables in the in_context definition
|
|
124
214
|
|
|
125
215
|
```ruby
|
|
126
|
-
define_context :
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
216
|
+
RSpec.define_context :interactor_contract do |required_fields|
|
|
217
|
+
required_fields.each do |field|
|
|
218
|
+
context "when #{field} is missing" do
|
|
219
|
+
let(field) { nil }
|
|
220
|
+
|
|
221
|
+
it "fails" do
|
|
222
|
+
expect(subject).to be_a_failure
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "reports the breach" do
|
|
226
|
+
expect(subject.breaches).to include(field)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
134
229
|
end
|
|
135
230
|
end
|
|
136
231
|
|
|
137
232
|
[...]
|
|
138
233
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
234
|
+
RSpec.describe CreateInvoice do
|
|
235
|
+
subject { described_class.call(amount: amount, client: client) }
|
|
236
|
+
|
|
237
|
+
let(:amount) { 100 }
|
|
238
|
+
let(:client) { create(:client) }
|
|
239
|
+
|
|
240
|
+
in_context :interactor_contract, %i[amount client]
|
|
143
241
|
end
|
|
144
242
|
```
|
|
145
243
|
|
|
146
244
|
#### Scoping
|
|
147
245
|
|
|
148
|
-
* In_contexts can be
|
|
246
|
+
* In_contexts can be scoped inside one another
|
|
149
247
|
|
|
150
248
|
```ruby
|
|
151
|
-
define_context :
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
249
|
+
RSpec.define_context :with_frozen_time do
|
|
250
|
+
before { freeze_time }
|
|
251
|
+
execute_tests
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
RSpec.define_context :with_inline_mailer do
|
|
255
|
+
around do |example|
|
|
256
|
+
ActiveJob::Base.queue_adapter = :inline
|
|
257
|
+
ActionMailer::Base.deliveries.clear
|
|
258
|
+
example.run
|
|
259
|
+
ActionMailer::Base.deliveries.clear
|
|
260
|
+
ActiveJob::Base.queue_adapter = :test
|
|
159
261
|
end
|
|
262
|
+
execute_tests
|
|
160
263
|
end
|
|
161
264
|
|
|
162
|
-
|
|
163
|
-
context 'and tree also' do
|
|
164
|
-
let(:tree) { 'abcd' }
|
|
265
|
+
[...]
|
|
165
266
|
|
|
166
|
-
|
|
167
|
-
|
|
267
|
+
RSpec.describe PasswordReset do
|
|
268
|
+
in_context :with_frozen_time do
|
|
269
|
+
in_context :with_inline_mailer do
|
|
270
|
+
it "sends the reset email with correct timestamp" do
|
|
271
|
+
PasswordReset.call(user)
|
|
272
|
+
expect(ActionMailer::Base.deliveries.last.body)
|
|
273
|
+
.to include(Time.current.to_s)
|
|
274
|
+
end
|
|
168
275
|
end
|
|
169
276
|
end
|
|
170
277
|
end
|
|
278
|
+
```
|
|
171
279
|
|
|
172
|
-
|
|
280
|
+
* You can also use `in_context` inside a `define_context` to compose contexts together:
|
|
173
281
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
end
|
|
282
|
+
```ruby
|
|
283
|
+
RSpec.define_context :statistics_processor do
|
|
284
|
+
in_context :interactor_contract, %i[account date]
|
|
178
285
|
|
|
179
|
-
|
|
286
|
+
it "succeeds" do
|
|
287
|
+
expect(subject).to be_success
|
|
288
|
+
end
|
|
180
289
|
end
|
|
181
290
|
```
|
|
182
291
|
|
|
@@ -187,29 +296,29 @@ end
|
|
|
187
296
|
* You can add a namespace to a in_context definition
|
|
188
297
|
|
|
189
298
|
```ruby
|
|
190
|
-
define_context "
|
|
299
|
+
define_context "with valid params", namespace: "users"
|
|
191
300
|
```
|
|
192
301
|
Or
|
|
193
302
|
```ruby
|
|
194
|
-
define_context "
|
|
303
|
+
define_context "with valid params", ns: "users"
|
|
195
304
|
```
|
|
196
305
|
Or
|
|
197
306
|
```ruby
|
|
198
|
-
RSpec.define_context "
|
|
307
|
+
RSpec.define_context "with valid params", ns: "users"
|
|
199
308
|
```
|
|
200
309
|
|
|
201
|
-
* When you want to use a namespaced in_context, you have two
|
|
310
|
+
* When you want to use a namespaced in_context, you have two choices:
|
|
202
311
|
|
|
203
|
-
Ignore any namespace and it will try to find a corresponding in_context in
|
|
312
|
+
Ignore any namespace and it will try to find a corresponding in_context in any namespace (the ones defined without namespace have the priority). **Note**: if the same context name exists in multiple namespaces, an `AmbiguousContextName` error will be raised — you must specify the namespace explicitly.
|
|
204
313
|
```ruby
|
|
205
314
|
define_context "namespaced context", ns: "namespace name" do
|
|
206
315
|
[...]
|
|
207
316
|
end
|
|
208
317
|
|
|
209
|
-
in_context "namespaced context"
|
|
318
|
+
in_context "namespaced context" # Works if only one namespace has this name
|
|
210
319
|
```
|
|
211
320
|
|
|
212
|
-
Pass a namespace and it will look only in this
|
|
321
|
+
Pass a namespace and it will look only in this namespace.
|
|
213
322
|
```ruby
|
|
214
323
|
define_context "namespaced context", ns: "namespace name" do
|
|
215
324
|
[...]
|
|
@@ -219,12 +328,12 @@ in_context "namespaced context", namespace: "namespace name"
|
|
|
219
328
|
in_context "namespaced context", ns: "namespace name"
|
|
220
329
|
```
|
|
221
330
|
|
|
222
|
-
#### Making `in_context`
|
|
331
|
+
#### Making `in_context` advertise itself
|
|
223
332
|
|
|
224
333
|
The fact that a `in_context` block is used inside the test is silent and invisible by default.
|
|
225
|
-
`in_context` will still wrap its own execution inside
|
|
334
|
+
`in_context` will still wrap its own execution inside an anonymous context.
|
|
226
335
|
|
|
227
|
-
But, there's some
|
|
336
|
+
But, there's some cases where it helps to make the `in_context` wrap its execution in a named `context` block.
|
|
228
337
|
For example:
|
|
229
338
|
```ruby
|
|
230
339
|
define_context "with my_var defined" do
|
|
@@ -247,7 +356,7 @@ end
|
|
|
247
356
|
Using a `rspec -f doc` will only print "MyNiceClass works" and "MyNiceClass doesn't work" which is not really a good documentation.
|
|
248
357
|
|
|
249
358
|
So, you can define a context specifying it not to be `silent` or to `print_context`.
|
|
250
|
-
For example
|
|
359
|
+
For example:
|
|
251
360
|
```ruby
|
|
252
361
|
define_context "with my_var defined", silent: false do
|
|
253
362
|
before do
|
|
@@ -268,12 +377,67 @@ end
|
|
|
268
377
|
```
|
|
269
378
|
Will print "MyNiceClass with my_var defined works" and "MyNiceClass without my_var defined doesn't work". Which is valid and readable documentation.
|
|
270
379
|
|
|
271
|
-
|
|
380
|
+
#### Thread-safety & parallel_tests
|
|
381
|
+
|
|
382
|
+
The context registry is protected by a Mutex so it's safe to use with `parallel_tests` in thread mode.
|
|
383
|
+
|
|
384
|
+
#### Memory cleanup
|
|
272
385
|
|
|
386
|
+
For long-running test suites with many dynamically generated contexts, you can free all stored contexts:
|
|
387
|
+
|
|
388
|
+
```ruby
|
|
389
|
+
RspecInContext::InContext.clear_all_contexts!
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Errors
|
|
393
|
+
|
|
394
|
+
| Error | Cause |
|
|
395
|
+
|---|---|
|
|
396
|
+
| `NoContextFound` | `in_context` refers to a name that doesn't exist or is out of scope |
|
|
397
|
+
| `AmbiguousContextName` | Same name exists in multiple namespaces, no namespace specified |
|
|
398
|
+
| `InvalidContextName` | `define_context` called with `nil` or empty name |
|
|
399
|
+
| `MissingDefinitionBlock` | `define_context` called without a block |
|
|
400
|
+
|
|
401
|
+
## Examples
|
|
402
|
+
|
|
403
|
+
The [`examples/`](examples/) directory contains real-world usage patterns:
|
|
404
|
+
|
|
405
|
+
- **`contexts/`** — Context definitions you'd put in `spec/contexts/` (authentication, interactor contracts, frozen time, job setup, mailer, composed contexts)
|
|
406
|
+
- **`usage/`** — Spec files showing how to use those contexts in practice
|
|
407
|
+
|
|
408
|
+
See [`examples/README.md`](examples/README.md) for the full list.
|
|
409
|
+
|
|
410
|
+
## Migrating to 1.2.0
|
|
411
|
+
|
|
412
|
+
### Breaking changes
|
|
413
|
+
|
|
414
|
+
- **Ruby >= 3.2 required.** Older Rubies are no longer supported.
|
|
415
|
+
- **`AmbiguousContextName` error.** If the same context name exists in multiple namespaces and you call `in_context` without specifying a namespace, `AmbiguousContextName` is now raised instead of silently picking one. Fix: add `ns:` to disambiguate.
|
|
416
|
+
- **`ActiveSupport` removed.** The gem no longer depends on `activesupport`. This should be transparent, but if you were relying on `HashWithIndifferentAccess` behavior from the gem's internals, note that contexts are now stored in a plain `Hash` with string-normalized keys (symbols and strings still work interchangeably).
|
|
417
|
+
|
|
418
|
+
### Deprecations
|
|
419
|
+
|
|
420
|
+
- **`instanciate_context`** is deprecated (typo). Use `instantiate_context` or `execute_tests` instead. The old method still works but emits a warning to `$stderr`.
|
|
421
|
+
|
|
422
|
+
### New features
|
|
423
|
+
|
|
424
|
+
- **Input validation**: `define_context` now raises `InvalidContextName` (nil/empty name) and `MissingDefinitionBlock` (no block).
|
|
425
|
+
- **`clear_all_contexts!`**: Call `RspecInContext::InContext.clear_all_contexts!` to free all stored contexts for memory cleanup in long-running suites.
|
|
426
|
+
- **Thread-safety**: The context registry is now protected by a Mutex for `parallel_tests` in thread mode.
|
|
427
|
+
|
|
428
|
+
## Development
|
|
273
429
|
|
|
274
430
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
275
431
|
|
|
276
|
-
After
|
|
432
|
+
After setting up the repo, you should run `overcommit --install` to install the different hooks.
|
|
433
|
+
|
|
434
|
+
Every commit/push is checked by overcommit.
|
|
435
|
+
|
|
436
|
+
Tool used in dev:
|
|
437
|
+
|
|
438
|
+
- RSpec
|
|
439
|
+
- Rubocop
|
|
440
|
+
- Prettier
|
|
277
441
|
|
|
278
442
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
279
443
|
|
data/TODO.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# TODO
|
|
2
|
+
|
|
3
|
+
Issues identified during review of the codebase and PR #21.
|
|
4
|
+
|
|
5
|
+
## PR #21 — Resolved
|
|
6
|
+
|
|
7
|
+
- [x] Stack leak if `find_context` raises — moved `find_context` before `push`
|
|
8
|
+
- [x] Indentation of `spec.metadata` in gemspec — fixed by prettier
|
|
9
|
+
- [ ] `.ruby-version` 4.0.1 vs CI matrix — `head` covers it, revisit if contributor confusion arises
|
|
10
|
+
|
|
11
|
+
## P0 — Before merge
|
|
12
|
+
|
|
13
|
+
- [x] Quote prettier globs in CI — quoted `'lib/**/*.rb'` etc. in both workflow files
|
|
14
|
+
- [x] Gitignore `.rubocop-remote-*.yml` — added pattern to `.gitignore`
|
|
15
|
+
|
|
16
|
+
## P1 — Réduction des dépendances
|
|
17
|
+
|
|
18
|
+
- [x] Retirer `active_support/all` — remplacé `HashWithIndifferentAccess` par `Hash` avec `.to_s` sur les clés, retiré `present?`. Temps de boot des tests : 0.27s → 0.08s
|
|
19
|
+
- [x] Retirer `faker` — retiré de `spec_helper.rb` et `rspec_in_context.gemspec`
|
|
20
|
+
|
|
21
|
+
## P1 — Robustesse face aux internals RSpec
|
|
22
|
+
|
|
23
|
+
- [x] Smoke test pour `hooks.instance_variable_get(:@owner)` — vérifie que `@owner` est non-nil et est une Class
|
|
24
|
+
- [x] Smoke test pour le prepend de `ContextManagement` sur `ExampleGroup.subclass`
|
|
25
|
+
- [x] Resserrer les contraintes de version — `rspec "~> 3.0"`, `rake "~> 13.0"`, `activesupport` retiré
|
|
26
|
+
|
|
27
|
+
## P2 — Qualité de l'API
|
|
28
|
+
|
|
29
|
+
- [x] Ajouter `RspecInContext::Error` comme classe de base — `NoContextFound` et `AmbiguousContextName` en héritent
|
|
30
|
+
- [x] Retirer le `instance_exec` inutile dans `define_context`
|
|
31
|
+
- [x] Corriger les typos dans les commentaires (`find` → `found`, `overriden` → `overridden`, `colisions` → `collisions`)
|
|
32
|
+
- [x] Corriger `instanciate_context` — ajouté `instantiate_context` comme alias principal, l'ancien émet un deprecation warning
|
|
33
|
+
|
|
34
|
+
## P2 — Tests
|
|
35
|
+
|
|
36
|
+
- [x] Renforcer les tests — block delivery guard dynamique (voir `spec/support/block_delivery_guard.rb`) détecte les blocs non-consommés et les groupes vides. Les `expect(true).to be_truthy` restants sont acceptables car le guard couvre la disparition silencieuse.
|
|
37
|
+
- [x] Sémantique de `test_inexisting_context` — vérifiée et documentée. Appelle `find_context` au runtime, qui est la première opération de `in_context`. Le comportement est identique.
|
|
38
|
+
|
|
39
|
+
## P3 — Nice to have
|
|
40
|
+
|
|
41
|
+
- [x] Ajouter `clear_all_contexts!` pour le nettoyage mémoire — `RspecInContext::InContext.clear_all_contexts!` réinitialise le registre. Test ajouté avec sauvegarde/restauration du state.
|
|
42
|
+
- [x] Ajouter un Mutex autour de `@contexts` — `@contexts_mutex` protège l'initialisation et le clear. Thread-safe pour `parallel_tests` en mode thread.
|
|
43
|
+
- [x] Corriger les typos restantes dans les commentaires — fait avec les fixes P2
|
|
44
|
+
- [x] Section migration dans le README — section "Migrating to 1.2.0" ajoutée avec breaking changes, deprecations, et nouvelles features. Mise à jour de la mention `instanciate_context` → `instantiate_context`.
|
data/examples/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Real-world usage examples for `rspec_in_context`.
|
|
4
|
+
|
|
5
|
+
## contexts/
|
|
6
|
+
|
|
7
|
+
Context definitions you'd put in `spec/contexts/` in a real project:
|
|
8
|
+
|
|
9
|
+
- **authenticated_request_context.rb** — Authentication setup with built-in unauthenticated test + `execute_tests` for your authenticated tests
|
|
10
|
+
- **interactor_contract_context.rb** — Parameterized contract validation for interactor-style service objects
|
|
11
|
+
- **frozen_time_context.rb** — Simple setup context using `freeze_time`
|
|
12
|
+
- **active_job_context.rb** — `around` hook to run jobs in test mode
|
|
13
|
+
- **inline_mailer_context.rb** — Delivers emails synchronously during tests
|
|
14
|
+
- **composed_context.rb** — Composing contexts: uses `in_context` inside `define_context`
|
|
15
|
+
|
|
16
|
+
## usage/
|
|
17
|
+
|
|
18
|
+
Spec files showing how to use the contexts above:
|
|
19
|
+
|
|
20
|
+
- **request_spec.rb** — Using `:authenticated_request` in a request spec
|
|
21
|
+
- **interactor_spec.rb** — Using `:interactor_expect` for contract validation
|
|
22
|
+
- **nested_contexts_spec.rb** — Nesting `:with_frozen_time` and `:with_inline_mailer`
|
|
23
|
+
- **composed_context_spec.rb** — Using `:service_processor` (which itself uses `:interactor_expect`)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Sets up ActiveJob in test mode for the duration of the tests.
|
|
2
|
+
# Useful when you need to assert on enqueued/performed jobs.
|
|
3
|
+
|
|
4
|
+
RSpec.define_context :with_test_jobs do
|
|
5
|
+
include ActiveJob::TestHelper
|
|
6
|
+
|
|
7
|
+
around do |example|
|
|
8
|
+
original_adapter = ActiveJob::Base.queue_adapter
|
|
9
|
+
ActiveJob::Base.queue_adapter = :test
|
|
10
|
+
example.run
|
|
11
|
+
ActiveJob::Base.queue_adapter = original_adapter
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
execute_tests
|
|
15
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# A context that sets up authentication, tests the unauthenticated case,
|
|
2
|
+
# then lets you inject your authenticated tests via execute_tests.
|
|
3
|
+
#
|
|
4
|
+
# Expects the caller to define:
|
|
5
|
+
# - http_method (e.g. :get, :post)
|
|
6
|
+
# - endpoint_path (e.g. users_path)
|
|
7
|
+
|
|
8
|
+
RSpec.define_context :authenticated_request do
|
|
9
|
+
let(:user) { create(:user) }
|
|
10
|
+
let(:account) { user.accounts.first }
|
|
11
|
+
|
|
12
|
+
before { sign_in user }
|
|
13
|
+
|
|
14
|
+
describe "authentication" do
|
|
15
|
+
context "without authentication" do
|
|
16
|
+
before { sign_out user }
|
|
17
|
+
|
|
18
|
+
it "redirects to login" do
|
|
19
|
+
send(http_method, endpoint_path)
|
|
20
|
+
|
|
21
|
+
expect(response).to redirect_to(new_user_session_path)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
execute_tests
|
|
27
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Example of composing contexts together.
|
|
2
|
+
# This context reuses :interactor_expect and adds a success test.
|
|
3
|
+
#
|
|
4
|
+
# Shows how in_context can be used inside define_context
|
|
5
|
+
# to build higher-level reusable blocks.
|
|
6
|
+
|
|
7
|
+
RSpec.define_context :service_processor do
|
|
8
|
+
in_context :interactor_expect, %i[account date]
|
|
9
|
+
|
|
10
|
+
it "succeeds with valid params" do
|
|
11
|
+
expect(subject).to be_success
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Delivers emails inline (synchronously) during tests.
|
|
2
|
+
# Clears the deliveries before and after each test.
|
|
3
|
+
|
|
4
|
+
RSpec.define_context :with_inline_mailer do
|
|
5
|
+
around do |example|
|
|
6
|
+
original_adapter = ActiveJob::Base.queue_adapter
|
|
7
|
+
ActiveJob::Base.queue_adapter = :inline
|
|
8
|
+
ActionMailer::Base.deliveries.clear
|
|
9
|
+
example.run
|
|
10
|
+
ActionMailer::Base.deliveries.clear
|
|
11
|
+
ActiveJob::Base.queue_adapter = original_adapter
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
execute_tests
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Validates that an interactor fails when required fields are missing.
|
|
2
|
+
# Pass the list of required fields as an argument.
|
|
3
|
+
#
|
|
4
|
+
# Expects the caller to define a subject that returns an interactor result,
|
|
5
|
+
# and a let for each required field.
|
|
6
|
+
|
|
7
|
+
RSpec.define_context :interactor_expect do |required_fields|
|
|
8
|
+
required_fields.each do |field|
|
|
9
|
+
context "when the field #{field} is not present" do
|
|
10
|
+
let(field) { nil }
|
|
11
|
+
|
|
12
|
+
it "fails" do
|
|
13
|
+
context = subject
|
|
14
|
+
expect(context).to be_a_failure
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "populates the breaches" do
|
|
18
|
+
context = subject
|
|
19
|
+
expect(context.breaches).to include(field)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Example: Using a composed context
|
|
2
|
+
#
|
|
3
|
+
# :service_processor reuses :interactor_expect internally,
|
|
4
|
+
# so you get contract validation + success test in one call.
|
|
5
|
+
|
|
6
|
+
require "rails_helper"
|
|
7
|
+
|
|
8
|
+
RSpec.describe DailyStatsProcessor do
|
|
9
|
+
subject { described_class.call(account: account, date: date) }
|
|
10
|
+
|
|
11
|
+
let(:account) { create(:account) }
|
|
12
|
+
let(:date) { Date.current }
|
|
13
|
+
|
|
14
|
+
in_context :service_processor
|
|
15
|
+
end
|