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.
data/README.md CHANGED
@@ -1,12 +1,69 @@
1
1
  # RspecInContext
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/rspec_in_context.svg)](https://badge.fury.io/rb/rspec_in_context)
4
- [![Codacy Badge](https://app.codacy.com/project/badge/Grade/6490834b08664dc898d0107c74a78357)](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
  ![Test and Release badge](https://github.com/zaratan/rspec_in_context/workflows/Test%20and%20Release/badge.svg)
6
5
 
7
- This gem is here to help you write better shared_examples in Rspec.
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 it a block ? There you go: `rspec_in_context`
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 Rspec
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 Rspec:
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 block that are reusable almost anywhere.
48
- They completely look like normal Rspec.
104
+ You can define in_context blocks that are reusable almost anywhere.
105
+ They completely look like normal RSpec.
49
106
 
50
- ##### Inside a Rspec block (scoped)
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 Rspec block (globally)
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 context see `RSpec.define_context`
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
- Rspec.describe MyClass do
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 chose exactly where your inside test will be used:
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 :context_name do
97
- it 'works' do
98
- expect(true).to be_truthy
99
- end
100
- context "in this context pomme exists" do
101
- let(:pomme) { "abcd" }
102
-
103
- execute_tests
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
- in_context :context_name do
110
- it 'will be executed at execute_tests place' do
111
- expect(pomme).to eq("abcd") # => true
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
- `instanciate_context` is an alias of `execute_tests` so you can't use both.
119
- But it let you describe what the block will do better.
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 variable in the in_context definition
213
+ * You can use variables in the in_context definition
124
214
 
125
215
  ```ruby
126
- define_context :context_name do |name|
127
- it 'works' do
128
- expect(true).to be_truthy
129
- end
130
- context "in this context #{name} exists" do
131
- let(name) { "abcd" }
132
-
133
- execute_tests
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
- in_context :context_name, :poire do
140
- it 'the right variable will exists' do
141
- expect(poire).to eq("abcd") # => true
142
- end
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 scope inside one another
246
+ * In_contexts can be scoped inside one another
149
247
 
150
248
  ```ruby
151
- define_context :context_name do |name|
152
- it 'works' do
153
- expect(true).to be_truthy
154
- end
155
- context "in this context #{name} exists" do
156
- let(name) { "abcd" }
157
-
158
- execute_tests
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
- define_context "second in_context" do
163
- context 'and tree also' do
164
- let(:tree) { 'abcd' }
265
+ [...]
165
266
 
166
- it 'will scope correctly' do
167
- expect(tree).to eq(poire)
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
- in_context :context_name, :poire do
175
- it 'the right variable will exists' do
176
- expect(poire).to eq("abcd") # => true
177
- end
282
+ ```ruby
283
+ RSpec.define_context :statistics_processor do
284
+ in_context :interactor_contract, %i[account date]
178
285
 
179
- in_context "second in_context" # => will work
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 "this is a namespaced context", namespace: "namespace name"
299
+ define_context "with valid params", namespace: "users"
191
300
  ```
192
301
  Or
193
302
  ```ruby
194
- define_context "this is a namespaced context", ns: "namespace name"
303
+ define_context "with valid params", ns: "users"
195
304
  ```
196
305
  Or
197
306
  ```ruby
198
- RSpec.define_context "this is a namespaced context", ns: "namespace name"
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 choice:
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 any_namespace (the ones defined without namespace have the priority);
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 context.
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` adverstise itself
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 a anonymous context.
334
+ `in_context` will still wrap its own execution inside an anonymous context.
226
335
 
227
- But, there's some case where it helps to make the `in_context` to wrap its execution in a named `context` block.
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
- ## Development
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 setuping the repo, you should run `overcommit --install` to install the different hooks.
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`.
@@ -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,8 @@
1
+ # Freezes time for the duration of the tests.
2
+ # Uses ActiveSupport's freeze_time.
3
+
4
+ RSpec.define_context :with_frozen_time do
5
+ before { freeze_time }
6
+
7
+ execute_tests
8
+ 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