In RSpec, what changes when ShoppingList spec setup moves from before(:example) to before(:context), and what stops working there?
answer
- :each and :all are aliases
- runs once per group
- instance variables copied as references
- let and subject raise
- doubles and transactions do not apply
basics
~10 sbefore(:context) runs once per group, not per example. Its instance variables reach every example as references to the same objects, so mutations leak between examples, and let, subject and test doubles are unsupported there.
solid answer
~40 s`before(:example)`, alias `before(:each)`, runs before every example; `before(:context)`, alias `before(:all)`, runs once before the group's first example, in a generated group instance. RSpec records the instance variables it sets and assigns them into each example's instance, so every example holds **the same object**: if one example calls `@list.add("milk")`, later examples see the milk, and random ordering turns that into intermittent failures. Reassigning `@list` inside an example does not leak, but mutating it does. Calling a `let` or `subject` from a context hook raises an error, and doubles or stubs declared there are not supported. Library-managed per-example cleanup such as database transactions also does not wrap it, so whatever it creates must be removed in `after(:context)`. Use it only for expensive, read-only setup.
go deeper
Know that :example runs per example and :context once per group, and that :each and :all are the older names for them.
Explain how before(:context) instance variables reach each example as shared references, and why let, subject and doubles are unsupported there.
Diagnose an order-dependent failure traced to a mutated before(:context) object, and decide which setup can safely stay at context scope.
Balance suite speed against shared-state risk when teams push setup to context scope, and set review rules for it.
## The scopes RSpec hooks take a **scope** argument that decides how often they run: | Scope | Alias | Runs | Where it may be declared | |---|---|---|---| | `:example` (default) | `:each` | before or after every example | any group or `RSpec.configure` | | `:context` | `:all` | once per group, around all its examples | any group or `RSpec.configure` | | `:suite` | none | once for the whole run | only `RSpec.configure`; in a group it is ignored with a warning | `before` hooks run outer to inner: configuration, then parent group, then current group, and within one group in declaration order. `after` hooks run in the reverse order. `around` hooks accept only the `:example` scope; `around(:context)` prints a warning and behaves like `around(:example)`. ## How before(:context) shares state A `before(:context)` block runs in an instance of the group created just for that purpose. When it finishes, RSpec **records every instance variable** that instance holds. Then, for each example, it creates the usual fresh group instance and assigns the recorded variables into it. The values are **references**, not copies. Two cases behave differently: - **Reassignment is private.** An example that writes `@list = ShoppingList.new` replaces the variable on its own instance only. - **Mutation is shared.** An example that calls `@list.add("milk")` changes the one `ShoppingList` object every example points at. ```ruby RSpec.describe ShoppingList do before(:context) { @list = ShoppingList.new } it "adds milk" do @list.add("milk") expect(@list.items).to eq(["milk"]) end it "starts empty" do expect(@list.items).to be_empty end end ``` With a fixed order the second example fails; with random ordering it fails only on some seeds. That is exactly the kind of intermittent failure `before(:example)` avoids by building a new list each time. ## What is not supported inside it RSpec's per-example constructs reset between examples and therefore have no meaning at context scope: 1. **`let` and `subject`**: accessing one from `before(:context)` or `after(:context)` raises an error that names the declaration and explains that it resets per example. 2. **Test doubles and stubs**: they are verified and torn down per example, so the rspec-core documentation lists any mocking, stubbing or double declaration as unsupported in `before(:context)`. 3. **Per-example cleanup provided by other libraries**: tools that wrap each example in a database transaction do not wrap a context hook, so records it creates survive the group. You must delete them yourself in `after(:context)`. ## The full order for one group For a group with hooks at every level, RSpec runs: 1. `before(:context)` hooks from `RSpec.configure`, then the parent group, then the current group, once. 2. For each example, the `around` hooks start and call `example.run`. 3. Inside that call, `before(:example)` hooks run outer to inner, then the example body, then `after(:example)` hooks inner to outer. 4. The `around` hooks finish their code after `example.run`. 5. After the last example, `after(:context)` hooks run from the current group outwards to configuration. `around` hooks never wrap context hooks, so a resource opened in `before(:context)` is not inside any `around` block. ## Failure behaviour - If a `before` hook raises, RSpec skips the remaining `before` hooks and the example but still runs the `after(:example)` and `after(:context)` hooks. - An error in a `before(:context)` hook fails every example in that group, which can make one broken fixture look like many broken behaviours. - `after` hooks always run, and an error in one is recorded while the rest still execute. ## When before(:context) is worth it It exists for setup that is **expensive and read-only**: parsing a large fixture file, starting an external process, writing a temporary file the examples only read. Guidelines: - Treat anything it creates as immutable; freeze it if you can. - Pair every resource it creates with an `after(:context)` that releases it. - Keep objects the examples modify in `let` or `before(:example)`. - If a group is slow because of `before(:example)`, measure first; moving setup to context scope trades speed for shared state that someone later mutates.
- In what order do an around hook, before(:context) and before(:example) run for one example?`before(:context)` runs first, once for the group. Then, per example, the `around` hook starts, and when it calls `example.run` the `before(:example)` hooks, the example and the `after(:example)` hooks run inside it. `after(:context)` runs after the last example, outside every `around` hook.
- What happens to an example if its around hook never calls example.run?RSpec does not run the example body; it marks the example as skipped with a message saying the around hook did not execute the example. The `before(:example)` and `after(:example)` hooks do not run either, since they execute inside `example.run`.
- Can a before(:suite) hook be declared inside an example group?No. `:suite` hooks exist independently of any group, so RSpec 3 only supports them on the configuration object; a `before(:suite)` declared in a group is ignored with a warning.
before(:context) is one whiteboard wheeled into a room for a whole day of meetings: each meeting may bring a fresh page taped over it, but anything written on the board itself is still there for the next meeting.
saying these in an interview costs you the question
- before(:all) and before(:context) are different hooks with different timing
- each example gets a deep copy of before(:context) instance variables
- let values are the right way to memoize expensive before(:context) setup
- a stub declared in before(:context) lasts for every example in the group
- records created in before(:context) are rolled back after each example
- around(:context) wraps the whole group once