Use when writing, reviewing, or cleaning up RSpec tests for Ruby and Rails codebases. Covers spec type selection, factory design, flaky test fixes, shared examples, deterministic assertions, test-driven development discipline, and choosing the best first failing spec for Rails changes. Also applies when choosing between model, request, system, and job specs.
88
85%
Does it follow best practices?
Impact
91%
1.49xAverage score across 3 eval scenarios
Passed
No known issues
Use this skill when the task is to write, review, or clean up RSpec tests.
Core principle: Prefer behavioral confidence over implementation coupling. Good specs are readable, deterministic, and cheap to maintain.
| Aspect | Rule |
|---|---|
| Spec type | Request > controller; model for domain; system only for critical E2E |
| Assertions | Test behavior, not implementation |
| Factories | Minimal — only attributes needed for the test |
| Mocking | Stub external boundaries, not internal code |
| Isolation | Each example independent; no shared mutable state |
| Naming | describe for class/method, context for scenario |
| First slice | Start at the highest-value boundary that proves behavior |
| TDD | Write test first, run it, verify failure, then implement |
THE WORKFLOW IS: PRD → TASKS → TESTS → IMPLEMENTATION
Tests are a GATE between planning and code.
NO implementation code may be written until:
1. The test EXISTS
2. The test has been RUN
3. The test FAILS for the correct reason (feature missing, not typo)
ONLY AFTER the test is validated can implementation begin.Write code before the test? Delete it. Start over.
No exceptions:
The gate cycle for each behavior:
rails-tdd-slices for the checkpoint format.Choose the first failing spec at the boundary that gives the strongest signal with the least setup:
| Change type | Best first spec |
|---|---|
| New endpoint, controller action, or API behavior | Request spec |
| New domain rule on an existing model | Model spec |
| New service object or orchestration flow | Service spec |
| Background job behavior | Job spec; add service/domain spec if logic is non-trivial |
| Rails engine route, install, or generator behavior | Engine request/routing/generator spec via rails-engine-testing |
| Bug fix | Reproduction spec at the boundary where the bug is observed |
Prefer the highest-value spec that proves the behavior end-to-end enough to matter. Only start lower in the stack when the boundary spec would be noisy, expensive, or unable to isolate the rule you need.
Monolith vs engine: For a normal Rails app, this skill applies as-is. When the project is a Rails engine, use rails-engine-testing for dummy-app setup, engine request/routing/generator specs, and host integration; keep using this skill for general RSpec style and structure.
describe, context, and it blocks.Bad: Vague test description:
# Not clear what's being tested, why, or under what conditions
it "should work correctly" do
# ...
endGood: Clear, behavior-driven test description:
# Explicitly states the behavior and the conditions under which it occurs
it "creates an order with pending status when valid parameters are provided" do
# ...
endspec/ (e.g. app/models/user.rb -> spec/models/user_spec.rb).let over let! when the value isn't needed for setup (improves test performance and clarity).let_it_be only if the project already includes test-prof; otherwise do not introduce it implicitly.For Rails work, the default decision order is:
request, job, engine route, or service entry point).Do not start with a low-level PORO spec if the real risk lives in request wiring, background execution, engine integration, or persistence behavior.
Before marking test work complete:
Can't check all boxes? You skipped TDD. Start over.
| Mistake | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Already manually tested" | Ad-hoc is not systematic. No record, can't re-run. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| Starting with the lowest layer by habit | Begin at the boundary that proves the behavior users care about |
| Testing mock behavior instead of real behavior | Mock returns what you told it to. Test the real thing. |
| Brittle assertions on internal calls | Assert outcomes, not implementation details. |
Excessive let! and nested contexts | Prefer let when value isn't needed for setup. Keep nesting shallow. |
Recommending let_it_be in every repo | Only use it when test-prof already exists in the project |
| Factories creating large graphs by default | Minimal factories — only what the test needs. |
| Time-sensitive tests without clock control | Use travel_to for time-dependent behavior. |
| "TDD is dogmatic, being pragmatic means adapting" | TDD IS pragmatic. Finds bugs before commit, enables refactoring. |
let! used everywhere instead of letWhen asked to improve tests:
| Skill | When to chain |
|---|---|
| rails-tdd-slices | When the hardest part is choosing the first failing Rails spec or vertical slice |
| rails-bug-triage | When a bug report must be turned into a reproducible failing spec and fix plan |
| rspec-service-testing | For service object specs (spec/services/) — instance_double, hash factories, shared_examples |
| rails-engine-testing | For engine specs — dummy app, routing specs, generator specs |
| rails-code-review | When reviewing test quality as part of code review |
| refactor-safely | When adding characterization tests before refactoring |
ae8ea63
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.