This commit renames the bundled RSpec core agent skill to follow the `discourse-` skill prefix convention. The skill directory and frontmatter name now use `discourse-writing-rspec-tests`, while the existing RSpec guidance and references remain unchanged. This keeps the skill naming consistent with the other Discourse-specific agent skills.
4.5 KiB
Vendored
RSpec Style Guide
Adapted from: https://rspec.rubystyle.guide/
Layout
-
No blank lines immediately after
feature,context, ordescribedeclarations -
One blank line between separate
describe/contextblocks; no blank line before closingend -
One blank line after
let,subject, andbefore/afterdeclarations before subsequent blocks -
Group
let/subjecttogether, separate frombefore/afterhooks with blank lines -
Surround multi-line declarations with blank lines — when a
let,let!,fab!, orsubjectuses ado...endor multi-line{ }body, put a blank line above and below it, even when adjacent declarations are single-line. Single-line declarations can still pack together, but a multi-line block always breathes on both sides:# bad — multi-line blocks stacked against neighbors fab!(:author) fab!(:topic) do Fabricate(:topic, user: author, title: "Hello") end fab!(:reply) do Fabricate(:post, topic: topic, user: author) end fab!(:tag) # good fab!(:author) fab!(:topic) do Fabricate(:topic, user: author, title: "Hello") end fab!(:reply) do Fabricate(:post, topic: topic, user: author) end fab!(:tag) -
One blank line before and after each
it/specifyblock -
Blank lines between logical chunks within an example — separate setup, action, and assertion for readability
Example Group Structure
- Declaration order:
subject→fab!/let!/let→before→after - Use
contextblocks to organize test conditions; avoid conditional logic in example descriptions - Pair context cases — include both positive and negative contexts (e.g. "when present" and "when not present")
- Use
fab!for shared test data,letfor computed values or non-persisted objects - Prefer
letover instance variables —let(:name) { "John" }notbefore { @name = "John" } - Omit
:each/:examplescope onbefore/after/aroundhooks (they're the default) - Use
:contextover:allwhen specifying hook scope - Minimize
:context-scoped hooks to prevent state leakage
Example Structure
- One expectation per example or use
aggregate_failurestag for multiple expectations; apply consistently - Use
subjectto eliminate repetition when multiple tests reference the same object - Name subjects explicitly —
subject(:article) { ... }not anonymoussubject { ... }(unless usingis_expected) - Use distinct subject names across different contexts for clarity
- Never stub methods on the subject — adjust initialization or create a presenter instead
- Use
specifyfor tests without descriptions; useitfor described examples - Don't generate tests via iteration — write each test explicitly
- Avoid incidental state — use matchers like
changeinstead of depending on shared state - Balance DRY with clarity — some duplication in tests is preferable to fragile shared setup
- Load only needed data — minimum objects required for the test
- Freeze time with
freeze_time— don't stubTime.noworDate.today - Stub HTTP requests with WebMock
- Don't define classes in example groups (they leak to global scope) — use
stub_constorClass.new - Use explicit block expectations —
expect { do_something }.to change(...)not implicit block subjects
Naming
- Context descriptions: start with "when", "with", or "without" — e.g.
context "when the user is logged in" - Example descriptions: prefer encoding the full scenario in the
itdescription rather than deeply nestingcontextblocks; limit nesting to 2 levels max - Keep descriptions under 60 characters
- Avoid "should" — use third-person present tense:
it "returns the summary"notit "should return the summary" - Describe methods:
.method_namefor class methods,#method_namefor instance methods
Expectations
- Always use
expectsyntax — nevershould
Matchers
- Use predicate matchers —
expect(article).to be_publishednotexpect(article.published?).to be true - Use built-in matchers —
expect(title).to include "lengthy"notexpect(title.include?("lengthy")).to be true - Avoid bare
be— usebe_truthy,be_nil,be_an(Type)etc. - Extract custom matchers for repeated expectation patterns
- Avoid
any_instance_of— mock injected dependencies directly
Capybara
- Use negative selectors —
have_no_selectornotto_not have_selector