$npx -y skills add marckohlbrugge/37signals-skills --skill rails-testingApply Rails testing standards with Minitest, fixtures, and pragmatic coverage boundaries. Use when creating tests, reviewing test quality, or improving flaky and slow Rails test suites.
| 1 | # Rails Testing |
| 2 | |
| 3 | Use for test-writing and test-review tasks. Patterns from Campfire and Fizzy test suites. |
| 4 | |
| 5 | ## Defaults |
| 6 | |
| 7 | - Minitest + fixtures. No RSpec, no FactoryBot. |
| 8 | - Test behavior, not implementation details. |
| 9 | - Keep tests deterministic and fast; `parallelize(workers: :number_of_processors)`. |
| 10 | - Tests ship in the same commit/PR as the feature — not before, not later. Security fixes always include a regression test. |
| 11 | - Never add production complexity for testability (no test-induced design damage). |
| 12 | |
| 13 | ## Coverage Budget (where 37signals actually spends) |
| 14 | |
| 15 | - **Heavy:** model tests (domain invariants, concerns) and controller/integration tests (full request cycle, auth, formats). |
| 16 | - **Light:** a few system tests for the critical happy paths (one smoke test can cover signup→use); job tests only for jobs with real logic. |
| 17 | - **None:** view tests, JS/Stimulus unit tests, exhaustive channel tests. UI behavior is covered indirectly by system tests. |
| 18 | - Don't duplicate the same behavior assertion at multiple layers. |
| 19 | |
| 20 | ## Fixtures |
| 21 | |
| 22 | - Express relationships by label, not ID; use ERB for relative timestamps (`created_at: <%= 1.hour.ago %>`) and shared computed values (one bcrypt digest reused). |
| 23 | - Mirror `app/models` structure in `test/models`: `app/models/card/closeable.rb` ↔ `test/models/card/closeable_test.rb`; shared concerns under `test/models/concerns/`. |
| 24 | - UUID PKs break fixture ordering: generate deterministic, label-derived UUIDv7s in fixtures so `.first`/`.last` are stable and runtime records are always newer. |
| 25 | - Build rich-content fixtures with production code (`ActionText::Attachment.from_attachable(user).to_html`), not hand-written markup. |
| 26 | |
| 27 | ## Good Practices |
| 28 | |
| 29 | - Use system/integration tests for user workflows; model tests for domain invariants. |
| 30 | - `travel_to` for time-based logic. |
| 31 | - Mock/stub only at boundaries (external APIs, network, time, `SecureRandom`); use VCR for external HTTP — auto-name cassettes from class+test name, normalize timestamps in matching. |
| 32 | - Test async side effects from model tests with `perform_enqueued_jobs(only: SpecificJob)` and `assert_enqueued_with` — not by unit-testing trivial job classes. |
| 33 | - Correlated count changes in one assertion: `assert_difference({ -> { card.assignees.count } => -1, -> { Event.count } => +1 })`. |
| 34 | - Test both response formats where controllers serve them: `as: :turbo_stream` (assert stream targets) and `as: :json` (status, Location header, body). |
| 35 | - Turbo/broadcast assertions by layer: `assert_turbo_stream_broadcasts` in model tests, `assert_turbo_stream action:, target:` in controller tests, `assert_no_turbo_stream_broadcasts` for negatives. |
| 36 | - Authorization tests assert the negative space: cross-tenant/role access returns 403/404, not just that allowed access works. |
| 37 | - Multi-tenant suites: set `Current.account` (and `Current.session` when behavior depends on the actor) in setup; integration/system tests set `default_url_options[:script_name]`; provide an `untenanted { }` helper for auth routes. Clear `Current` in teardown. |
| 38 | - Test middleware in isolation with `Rack::MockRequest`. |
| 39 | - System tests: `using_session("Kevin")` for multi-user scenarios; wait for cable connection before asserting realtime; auth via a fast session-transfer helper, keeping the full login flow to one smoke test. |
| 40 | - Suites with non-transactional side effects (FTS tables) opt out per-helper: `self.use_transactional_tests = false` + explicit cleanup. |
| 41 | - Reset shared global state per test in parallel suites (thread pools, `ActionCable.server.pubsub`, `Current`). |
| 42 | |
| 43 | ## Red Flags |
| 44 | |
| 45 | - Adding production complexity only for testability. |
| 46 | - Over-mocking internal app code. |
| 47 | - Duplicate tests for the same behavior at multiple layers. |
| 48 | - Slow suites caused by unnecessary setup in each test (that's what fixtures are for). |
| 49 | - Unit tests for one-line job classes or trivial delegations. |
| 50 | - Hand-rolled HTML strings where production renderers/helpers would stay in sync automatically. |
| 51 | - Time-dependent assertions without `travel_to`. |