| name | verify-specs |
| description | Use before creating, modifying, reviewing, or refactoring spec test files. Better Specs compliance, especially around one behavior per example, meaningful example names, describe/context structure, behavior-over-implementation assertions, `expect` syntax, or spec readability and maintainability problems. |
| license | MIT |
| metadata | {"author":"Airton Ponce @ton-anywhere"} |
Verify Specs
Overview
Review RSpec code against Better Specs and return concrete, file-specific findings. Focus on issues that make specs misleading, brittle, hard to debug, or unnecessarily hard to read.
Review Mode
Treat this as a spec review, not a generic style pass. Prioritize problems that affect failure isolation, behavior coverage, and maintainability before reporting cosmetic cleanup.
Workflow
-
Mandatory First Pass — Run both mechanical audits before touching anything else:
Skip either audit = incomplete review.
-
BDD Phase Pass — Classify each example into Given, When, and Then before proposing rewrites. Skip this = incomplete review.
-
Identify the spec type: isolated unit spec, request spec, integration/system spec, or another slow end-to-end flow.
-
Apply the high-signal checklist using the audit results from step 0 and the BDD phase pass as starting evidence.
-
Apply the secondary checklist only when it improves clarity without forcing churn.
-
Report findings as narrow rewrites tied to the current file.
Mandatory First Pass — Condition Word Audit
Before applying any other checklist items, run the audit script against the specific spec file under review:
ruby skills/verify-specs/scripts/condition_word_audit.rb spec/path/example_spec.rb
For whole-project reviews only, run:
ruby skills/verify-specs/scripts/condition_word_audit.rb --all
The script flags candidate it/specify descriptions containing when, with, without, for, or and.
Procedure:
- Run the script for the predetermined spec file, or
--all only when reviewing the whole project.
- For each candidate, classify whether the word introduces an input/state/branch condition (flag → move to context), signals a split behavior (
and → consider separate examples), or is intrinsic to the expected behavior (allow).
- Use the candidates as starting evidence for the review.
- Continue the full scan for all other Better Specs issues. The script is only an aid; it does not replace reviewing expectations, setup/action/assertion separation, behavior-vs-implementation, coverage, or structure.
Failure mode to prevent: Spotting obvious offenders at the top/bottom of a file and stopping there instead of auditing all examples systematically. A missed condition-word candidate means you did not complete Step 0 of your workflow.
Mandatory Expectation Count Audit
Before applying any other checklist items, run the expectation count audit against the specific spec file under review:
ruby skills/verify-specs/scripts/expectation_count_audit.rb spec/path/example_spec.rb
For whole-project reviews only, run:
ruby skills/verify-specs/scripts/expectation_count_audit.rb --all
The script flags it/specify blocks containing more than one expect(...).
Procedure:
- Run the script for the predetermined spec file, or
--all only when reviewing the whole project.
- For each flagged example, classify whether the expectations describe separate behaviors or one flow outcome.
- In isolated unit/service specs, split separate behaviors into separate examples.
- Allow multiple expectations only for non-isolated or expensive integration-style flows where the checks verify one outcome.
- If allowing a flagged example, state why it is a single behavior.
Failure mode to prevent: Running only the condition-word audit and missing body-level multi-expectation examples. Running the audit script alone is not a verify-specs review.
Mandatory BDD Phase Rule
For isolated RSpec examples, enforce a strict Given/When/Then split:
- Given: setup, stubs, doubles, factories, and preconditions belong in
let, let!, or before.
- When: the action under test belongs in a named
let(:result) or a before block.
- Then: each
it block contains only the expectation for that behavior.
Hard rules:
allow(...) is setup. Do not leave allow inside an it block. Move it to the nearest relevant before.
let(:result) { ... } or another clearly named result/action helper is the preferred place for the action under test.
- Use
let! or before { result } when the action must run before a have_received expectation.
- An isolated
it block should have a single expectation. Split examples when expectations describe different behaviors.
- Do not add a return-value expectation just to force a lazy
let(:result) to run. If the behavior under test is a spy expectation, make the action eager with let! or before { result }.
- Do not "fix" inline setup by moving setup to
before while leaving the action in the it block. That still mixes When and Then.
Exception:
- Error assertions such as
expect { action }.to raise_error(...) keep the action inside the expectation because the action is inseparable from the Then clause.
High-Signal Checklist
- Check that each isolated example tests one behavior. Flag multiple expectations in a single unit-style
it block when they represent separate behaviors.
- Allow exceptions for non-isolated specs with expensive setup, such as database-heavy flows, external service integration, or end-to-end behavior. In those cases, flag only true multi-behavior examples, not multiple checks for the same flow outcome.
- Require meaningful example names. Flag vague names like
works, returns the value, or descriptions that document implementation steps instead of behavior.
- Treat condition words in
it descriptions as structure smells. when, with, without, or for inside an example name usually means the condition belongs in a context, leaving the it description as the Then/expected behavior. Apply for with judgment: flag it when it introduces input, state, or branch conditions, not when it is intrinsic to the expected behavior.
- Treat
and in it descriptions as a split-example smell. It often signals two expected behaviors or multiple expectations that should become separate it blocks, especially in isolated specs.
- Prefer behavior over implementation. Flag tests that pin private methods, callback order, framework internals, SQL shape, or incidental collaborator calls unless those interactions are the public contract.
- Avoid global stubs for nondeterminism (
Kernel.rand, Time.now, process-wide constants, global config) unless the production code already exposes that dependency globally. Prefer deterministic inputs, dependency injection, or observable behavior at the service boundary.
- Check structure. Use
describe for the unit under test and context for conditions. Prefer method-style names like .call and #valid?. Nested context blocks are acceptable when they separate distinct Given/When conditions and keep example names focused.
- Treat
and in context descriptions as a nesting smell. It often means the context is combining two conditions that may deserve nested contexts.
- Keep descriptions short. If an example name becomes long or conditional, move the condition into a
context.
- Check that each
it block contains only assertions (Then), not setup (Given) or actions (When). Flag inline allow, factories, doubles, assignments, and method calls like service.do_something(...) inside the it body. Move setup to before/let, move the action to let(:result)/before, and leave one expectation in the example. Exception: error-raising tests using expect { ... }.to raise_error are inseparable — action is part of the expectation.
Secondary Checklist
- Prefer
expect syntax over should. Use is_expected.to for one-line implicit-subject expectations.
- Prefer context names that begin with
when, with, or without when they describe setup conditions.
- Prefer
let over instance variables in before blocks for named reusable setup. Use let! only when eager setup is required.
- Prefer factories over fixtures or oversized inline setup when the project already uses factories.
- Create only the data needed for the current behavior.
- Check valid, invalid, and edge paths when the production code branches.
- Consider shared examples when the same behavior is duplicated across files or contexts.
Reporting Findings
- Report only issues that materially improve clarity, failure isolation, or maintainability.
- For each finding, name the violated guideline, explain the risk in one sentence, and propose a narrow rewrite.
- If a spec intentionally trades purity for integration-test cost, say so explicitly instead of forcing the isolated-unit rule.
- If the spec is already in good shape, say that directly.
Rewrite Patterns
- Split a multi-assertion isolated example into multiple
it blocks.
- Replace vague example names with user-visible behavior.
- Move setup conditions from
it descriptions into context blocks.
- Split combined context conditions into nested
context blocks when that makes the Given/When structure clearer.
- Rewrite
it "returns empty array for nil vector" as context "with nil vector input" + it "returns an empty array".
- Replace implementation-level expectations with observable outputs, state changes, responses, jobs, side effects, or raised errors.
- Convert
should syntax to expect or is_expected.
- Split mixed setup/action/assertion examples by moving preconditions into
before/let, the action into let(:result) or before, and leaving only one assertion in the it block.
Spy Expectation Pattern
Bad:
it "does not call KeywordSearch" do
allow(Rag::Search::KeywordSearch).to receive(:call)
described_class.call(query: "", locale: :en)
expect(Rag::Search::KeywordSearch).not_to have_received(:call)
end
Good:
before { allow(Rag::Search::KeywordSearch).to receive(:call) }
let!(:result) { described_class.call(query: "", locale: :en) }
it "does not call KeywordSearch" do
expect(Rag::Search::KeywordSearch).not_to have_received(:call)
end
Do not add expect(result).to eq(...) to this example unless the return value is the behavior under review. That would create a second Then.
Reference
Read references/better-specs-review-reference.md when you need the detailed checklist, Better Specs examples, or the exception cases around slow non-isolated specs.