$npx -y skills add thoughtbot/rails-consultant --skill test-driven-developmentStrict red-green-refactor TDD workflow for implementing features, fixing bugs, or changing behavior in Rails applications. Enforces the discipline of writing a failing test before any production code. Use whenever you want to implement with TDD — whether a new feature, a bugfix,
| 1 | # Test-Driven Development (TDD) |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Write the test first. Watch it fail. Write minimal code to pass. |
| 6 | |
| 7 | **Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing. |
| 8 | |
| 9 | **Violating the letter of the rules is violating the spirit of the rules.** |
| 10 | |
| 11 | ## The Iron Law |
| 12 | |
| 13 | ``` |
| 14 | NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST |
| 15 | ``` |
| 16 | |
| 17 | Write code before the test? Delete it. Start over. |
| 18 | |
| 19 | **No exceptions:** |
| 20 | |
| 21 | - Don't keep it as "reference" |
| 22 | - Don't "adapt" it while writing tests |
| 23 | - Don't look at it |
| 24 | - Delete means delete |
| 25 | |
| 26 | Implement fresh from tests. Period. |
| 27 | |
| 28 | ## Outside-In Development |
| 29 | |
| 30 | Start every feature with a high-level test that describes behavior from the user's perspective. Run it, read the failure, and let that failure dictate your next move. As failures push you down the stack, write a new failing test at each layer you drop into — never write code for a layer without a failing test at that layer demanding it. |
| 31 | |
| 32 | Read `examples/outside-in-testing.md` for a walkthrough of the philosophy and `examples/testing-pyramid.md` for how test types combine into an optimal suite. |
| 33 | |
| 34 | ### One Change, One Run |
| 35 | |
| 36 | After every change — writing a test, adding a route, creating a file, implementing a method — run the affected test immediately: |
| 37 | |
| 38 | ```bash |
| 39 | bundle exec rspec spec/features/guest_searches_for_items_spec.rb |
| 40 | ``` |
| 41 | |
| 42 | The failure message is your instruction for what to do next. Don't batch changes and don't guess ahead: one change, one run, read the failure, decide. If you made two changes before running, you no longer know which one the test is reacting to. |
| 43 | |
| 44 | ### Drop Down by Writing the Next Failing Test |
| 45 | |
| 46 | A failure rarely means "write this exact line." It usually means "the layer below isn't there yet." When the active test's failure points to a layer that has behavior of its own — a controller action, an endpoint, a model method — **drop down and write a failing test at that layer before building it.** |
| 47 | |
| 48 | Think of it as a stack of failing tests: |
| 49 | |
| 50 | - The **active** test is the one whose failure you're reading right now. |
| 51 | - Its failure points to a missing lower layer → write a new failing test at that layer. It becomes the active test. |
| 52 | - Drive that test with Red-Green-Refactor. If _it_ forces you down another level, push another failing test. |
| 53 | - When the active test goes green, pop it: rerun the test one layer up. Its next failure drives the next move. |
| 54 | |
| 55 | Drop to whichever layer the failure names — not always one rung at a time. Continue until the top-level feature spec is green with nothing left on the stack. |
| 56 | |
| 57 | The ladder of test types, top to bottom: |
| 58 | |
| 59 | | When the failure points to… | Write this failing test | |
| 60 | | ------------------------------------------------------------------- | ---------------------------- | |
| 61 | | End-to-end behavior from the user's perspective | Feature / system spec | |
| 62 | | A controller action, response, status, or redirect | Request (or controller) spec | |
| 63 | | Logic in a model, service object, query, calculation, or validation | Model / unit spec | |
| 64 | |
| 65 | Feature and request specs are integration tests: real database records, no mocks — except external services (use webmock or fakes), so the suite runs offline. Unit specs isolate the object under test: mock collaborators aggressively, because the goal is to prove _this_ object, not its collaborators. Difficulty testing two objects in isolation signals too-tight coupling. |
| 66 | |
| 67 | ### Build Directly Only for Inert Glue |
| 68 | |
| 69 | A few things have no behavior of their own, so they get no test of their own — but you still add them only because a failing test one layer up demanded them: |
| 70 | |
| 71 | - The route line (a request spec failing with `No route matches` drives it) |
| 72 | - An empty class or module to clear a `NameError` |
| 73 | - Trivial markup a feature spec's content expectation already covers |
| 74 | |
| 75 | Everything with behavior gets its own failing test first. When in doubt, drop down and write the test. |
| 76 | |
| 77 | The testing pyramid: many unit tests at the bottom, fewer request specs in the middle, a few feature specs at the top. Unit tests are fast and precise; feature tests prove the system works end-to-end. Each plays to its strengths. |
| 78 | |
| 79 | ## Red-Green-Refactor |
| 80 | |
| 81 | The cycle every test follows — feature spec, request spec, and model spec alike. Each failing test on the stack runs this loop; a test going green is what lets you pop back up to the layer above. |
| 82 | |
| 83 | ```dot |
| 84 | digraph tdd_cycle { |
| 85 | rankdir=LR; |
| 86 | red [label="RED\nWrite failing test", shape=box, style=filled |