.skills/discourse-writing-rspec-tests/SKILL.md
Discourse uses RSpec for testing. Follow these patterns for all test types.
it block verifies one behavior for clear failure diagnosis.SomeService.expects(:some_method).never (or .once, .with(...)) couple the test to internal implementation details that the caller shouldn't care about. Assert on the observable outcome instead: returned value, persisted state, emitted event, response body, rendered output. If the implementation is later refactored, inlined, or renamed, a behavior-focused test still passes when the behavior is correct.shared_examples/let chains that hurt readability.describe/context block to confirm the test belongs there. Check that the parent context's description, let/fab! setup, and before hooks match the scenario being tested. A misplaced test inherits the wrong setup and produces misleading results.|vote|, |option|, not |v|, |o|.contain_exactly or eq instead of multiple include/not_to include checks.expect(response.body).not_to include("hidden data explorer excerpt") silently passes if the literal has a typo or drifts from the source, giving a false sense of security. Reference the object directly (expect(response.body).not_to include(private_post.raw)) so the assertion stays in sync with the data under test. The same applies to any not_to include/not_to match against hardcoded strings.describe/context nesting. Instead of deeply nested contexts, put the full scenario description in the it block itself. Flat tests are easier to read and maintain."returns true when topic_approval_type is approval or pre_approval" over "returns true when topic_approval_type is not none". Be specific about the values being tested.Before writing any RSpec code, read references/rspec-style-guide.md and apply those conventions alongside the Discourse-specific patterns below.
Use fab! for shared test data, or Fabricate inline within the test example:
fab!(:user)
fab!(:category)
fab!(:tag)
fab!(:topic) { Fabricate(:topic, category: category, tags: [tag]) }
it "displays the topic" do
sign_in(user)
visit("/")
end
it "creates a new topic" do
new_category = Fabricate(:category, name: "Special")
# ... test using new_category
end
Never use before blocks for Fabricate calls. Use let! only when absolutely necessary.
Tests have setup overhead. Optimize for the fewest test examples possible:
it block when testing the same page/stateit block incurs setup overhead; batch checks where logical# Run specific file
bin/rspec spec/models/topic_spec.rb
# Run specific line
bin/rspec spec/models/topic_spec.rb:15
describe grouping, and what to assert.See references/tracking-helpers.md for DiscourseEvent.track_events, MessageBus.track_publish, and track_sql_queries — block helpers that capture events, message-bus publishes, and SQL queries so tests can assert on side effects.