In RSpec, what is the implicit subject of RSpec.describe ShoppingList, and how do named subjects and is_expected change a spec?
answer
- the described class, instantiated
- described_class.new with no arguments
- is_expected wraps expect(subject)
- subject(:list) aliases a let
- super in a named subject raises
basics
~20 sWhen the outer group describes a class, subject is ShoppingList.new, built lazily and memoized per example like a let. subject(:list) { ... } replaces it and also names it; is_expected is shorthand for expect(subject) in one-line examples.
solid answer
~40 s`RSpec.describe ShoppingList` makes `subject` return `described_class.new`, memoized per example; nested string groups such as `describe "#add"` keep the outer class as `described_class`. If the first argument is not a class, for example `describe [1, 2]`, the subject is that object itself. The implicit subject calls `new` with no arguments, so a constructor with required arguments needs an explicit `subject { ShoppingList.new(owner: "ana") }`. Giving it a name, `subject(:list) { ... }`, defines a `let(:list)` and aliases `subject` to it, so one-liners and named references share one object; calling `super` inside a named subject block raises `NotImplementedError`. `is_expected` returns `expect(subject)`, which makes `it { is_expected.to be_empty }` read cleanly and lets RSpec generate the example description from the matcher. The older `it { should be_empty }` one-liner is what `is_expected` replaces.
go deeper
Recall that describing a class gives an implicit subject built with new, and that is_expected means expect(subject).
Explain the described_class lookup through nested groups, the no-argument constructor trap, and that a named subject is a let aliased to subject.
Judge when a named subject clarifies a spec and when a one-liner hides setup a reviewer needs to see.
Decide whether a team standard favours explicit named subjects over implicit ones, given how often constructors gain required arguments.
## What subject is `subject` is a memoized helper that behaves like a `let` named `:subject`. It represents **the object under test** for an example group. It is lazy and cached per example: the first call builds it, later calls in the same example return the same object, and the next example gets a fresh one. ## The implicit subject If a group never declares a subject, RSpec derives one from the group's first argument: 1. RSpec looks up `described_class`. For `RSpec.describe ShoppingList` that is `ShoppingList`. For a nested group whose first argument is a string (`describe "#add"`) or absent, it is inherited from the parent group. 2. If there is no described class, RSpec takes the group's first description argument instead. 3. If the result is a class, the subject is `that_class.new`; otherwise it is the object itself. | Group declaration | Implicit `subject` | |---|---| | `RSpec.describe ShoppingList` | `ShoppingList.new` | | `describe "#add"` nested inside it | still `ShoppingList.new` | | `RSpec.describe [1, 2, 3]` | the array `[1, 2, 3]` | | `RSpec.describe "shopping rules"` | the string `"shopping rules"` | The class case calls `new` with **no arguments**. If `ShoppingList#initialize` requires an `owner:` keyword, any example touching the implicit subject fails with `ArgumentError`, and you must declare the subject explicitly. ## Explicit and named subjects ```ruby RSpec.describe ShoppingList do subject(:list) { ShoppingList.new(owner: "ana") } it { is_expected.to be_empty } it "accepts new items" do list.add("milk") expect(list.items).to eq(["milk"]) end end ``` - `subject { ... }` replaces the implicit subject with your block. - `subject(:list) { ... }` does the same and also defines `list` as a `let`, then aliases `subject` to it. The one-liner and the named example operate on the **same** memoized object within an example. - Inside a **named** subject block, `super` is not supported and raises `NotImplementedError`. An unnamed subject defined in a nested group can call `super()` to reach the outer one. - `subject!` is to `subject` what `let!` is to `let`: it adds a `before` hook so the subject is built before every example. ## is_expected and one-liners `is_expected` is defined as `expect(subject)`. Its reason to exist is the **one-liner** style, where an example has no description and RSpec generates one from the matcher: - `it { is_expected.to be_empty }` is reported as "is expected to be empty". - `it { is_expected.not_to include("milk") }` reads as a sentence in the documentation output. Before the `expect` syntax, the same one-liner was written `it { should be_empty }`, calling `should` on the implicit receiver. RSpec 3 still accepts that form inside one-liners, but `is_expected` is the style to write today. ## described_class in examples `described_class` is also available inside examples and hooks. Writing `described_class.new(owner: "ana")` instead of `ShoppingList.new(owner: "ana")` keeps the spec correct when the class is renamed and lets shared examples build instances of whichever class includes them. - It returns the first argument of the nearest enclosing group whose first argument is not a string, which in practice is a class or module. - It returns `nil` when no enclosing group describes a class, for example a top-level `RSpec.describe "shopping rules"`. - It is read from group metadata, so it costs nothing and never builds an object, unlike `subject`. ## Using subject well - **Name it** when examples refer to it explicitly. RSpec's own documentation recommends intention-revealing names over bare `subject` calls in example bodies. - **Keep one-liners for simple properties** of the object; an example that needs setup, an action and several expectations deserves a written description. - **Do not call it from `before(:context)`**: like `let`, it resets per example, and RSpec raises an error if a context-level hook accesses it. - **Remember it is lazy**: a `before` block that mutates `subject` is what first builds it, which is fine, but a spec that expects a constructor side effect without referencing `subject` needs `subject!`.
- What does subject return in a group declared as `RSpec.describe [1, 2, 3]`?The array itself. RSpec only calls `new` when the described object is a class; any other object passed as the first argument is returned as the subject unchanged.
- Why does `it { is_expected.to be_empty }` print a readable description without a string?An example without a description asks its last matcher for one. `is_expected` sets an expectation on the subject, and `be_empty` describes itself, so the documentation output shows "is expected to be empty".
saying these in an interview costs you the question
- the implicit subject is the class ShoppingList itself, not an instance
- subject is built once per group and shared by its examples
- a nested describe "#add" group has no implicit subject
- is_expected evaluates the subject inside a block like expect { }
- subject(:list) creates two separate objects, list and subject