[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M786KX4DAY0F240GCNDAVYGF":3,"skill-same:01M786KX4DAY0F240GCNDAVYGF":49},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":29,"commitSha":30,"skillDirectory":31,"forksCount":14,"repositoryHtmlUrl":32,"repositoryHomepageUrl":6,"ownerHtmlUrl":33,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":34,"install":35,"resources":48},"01M786KX4DAY0F240GCNDAVYGF","ruby-testing",null,"Use ONLY when user explicitly requests Ruby test code, RSpec configuration, test failure diagnosis, or coverage improvement for Ruby projects.\nTriggers: \"write test ruby\", \"rspec\", \"rails test\", \"factory bot\", \"simplecov\", \"vcr test\", \"shared examples\", \"shoulda matchers\".\nDo NOT activate for: general Ruby coding without test context, gem\u002Fbundler issues, Rails model\u002Fcontroller implementation, or Ruby version management.","chenziyang110\u002Flaunchdeck","chenziyang110","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43693913?v=4","01M7863EBWK1D0P6A29YGEMAS4","Robin11","01M7863ECM1VB6RXDMHTW90D2T",0,"MIT","https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck\u002Fblob\u002Fmain\u002F.codex\u002Fskills\u002Fruby-testing\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: \"ruby-testing\"\ndescription: \"Use ONLY when user explicitly requests Ruby test code, RSpec configuration,\ntest failure diagnosis, or coverage improvement for Ruby projects.\n\nTriggers: \\\"write test ruby\\\", \\\"rspec\\\", \\\"rails test\\\", \\\"factory bot\\\",\n\\\"simplecov\\\", \\\"vcr test\\\", \\\"shared examples\\\", \\\"shoulda matchers\\\".\n\nDo NOT activate for: general Ruby coding without test context, gem\u002Fbundler\nissues, Rails model\u002Fcontroller implementation, or Ruby version management.\n\"\ncompatibility: \"Requires spec-kit project structure with .specify\u002F directory\"\nmetadata:\n  author: \"github-spec-kit\"\n  source: \"templates\u002Fpassive-skills\u002Fruby-testing\u002FSKILL.md\"\n---\n\n\n# Ruby Testing (Agent Skill)\n\nAgent-focused testing workflow for modern Ruby (3.2+) using RSpec, the dominant BDD-style test framework for Ruby.\n\n## When to Use\n\n- Writing new Ruby tests or fixing existing tests\n- Designing unit\u002Fintegration test coverage for Ruby components\n- Adding test coverage, CI gating, or regression protection\n- Configuring RSpec workflows for consistent execution\n- Investigating test failures or flaky behavior\n- Testing Rails apps, gems, or Sinatra services\n\n### When NOT to Use\n\n- Implementing new product features without test changes\n- Large-scale refactors unrelated to test coverage or failures\n- Performance tuning without test regressions to validate\n- Non-Ruby projects or non-test tasks\n\n### Stop Conditions — When to ASK instead of generating\n\nBefore generating any test code, STOP and ask the user if:\n- The code under test has **zero testability** (monkey-patched classes, hard-coded HTTP, no dependency injection)\n- **No test framework** is detected (no rspec\u002Fminitest in Gemfile, no spec\u002F directory)\n- The request is **ambiguous**: \"test my app\" or \"add tests\" without specifying files or classes\n- A **required test dependency** (rspec, rspec-rails, factory_bot) is not installed\n- The test would require **secrets, real credentials, or production database access**\n\n## Core Concepts\n\n- **TDD loop**: red → green → refactor (tests first, minimal fix, then cleanups).\n- **BDD style**: `describe` \u002F `context` \u002F `it` blocks for readable specifications.\n- **Test layout**: `spec\u002F` directory mirroring `lib\u002F` structure, `spec\u002Fspec_helper.rb`.\n- **Mocks**: RSpec's built-in `double()`, `allow()`, `expect()`.\n- **Factory pattern**: `factory_bot` for test data creation.\n- **CI signal**: `bundle exec rspec --format progress --color`.\n\n## Agent Execution Protocol\n\nWhen this skill is activated, follow this exact execution sequence:\n\n### Phase 1: Context Analysis\nBefore writing any test code:\n1. Identify the Ruby version (3.2+) and framework (RSpec, Minitest).\n2. Check for existing test files (`spec\u002F`, `test\u002F`) to infer naming conventions and style.\n3. Analyze the code under test: identify public API surface, side effects, ActiveRecord interactions, and async patterns.\n4. Determine request type: (a) new tests, (b) fixing broken tests, (c) coverage improvement, (d) flaky test diagnosis.\n\n### Phase 2: Strategy Selection\nBased on Phase 1, select the appropriate strategy:\n- **Pure functions \u002F utilities**: unit tests only, no mocks. Use `describe`\u002F`it` with `expect`.\n- **External dependencies (DB, HTTP, filesystem)**: use RSpec doubles\u002Fmocks; prefer `factory_bot` for data, `VCR` for HTTP recording, `DatabaseCleaner` for DB isolation.\n- **Rails applications**: use `type: :request` for API tests, `type: :model` for model tests; use `travel_to` for time freezing.\n- **Async code**: Ruby is mostly synchronous; use `have_received` for mock verification.\n\n### Decision Tree (Complex Scenarios)\n\n```text\nQ1: External I\u002FO (DB, HTTP, filesystem)?\n  → Yes → Q2\n  → No  → Pure unit test, no mocks needed\n\nQ2: Mockable (DI, service object, HTTP client)?\n  → Yes → Mock at boundary (RSpec doubles, VCR cassettes)\n  → No  → Q3\n\nQ3: Needs real DB behavior?\n  → Yes → DatabaseCleaner + factory_bot + in-memory\u002Ftest DB\n  → No  → Use simple double\u002Fstub\n\nQ4: Rails request\u002Fcontroller spec?\n  → Yes → type: :request with rspec-rails helpers\n  → No  → Q5\n\nQ5: Time-dependent code?\n  → Yes → travel_to \u002F Timecop for time freezing\n  → No  → Standard describe\u002Fit with expect()\n```\n\n### Phase 3: Test Case Design\nApply systematic test design before coding:\n1. **Happy path**: normal input → expected output\u002Fresponse.\n2. **Boundary values**: empty arrays, `nil`, empty strings, `0`, `Time.at(0)`.\n3. **Error cases**: exceptions (`raise_error`), validation errors, 4xx\u002F5xx responses, nil pointer scenarios.\n4. **State & database**: shared mutable state, `let` vs `let!` evaluation order, transaction isolation.\n\n### Phase 4: Code Generation\nOutput tests in this exact structure:\n```\n#### Test Strategy Summary\n- [ ] Scope: Unit \u002F Request \u002F Feature \u002F System\n- [ ] Framework: RSpec + factory_bot + VCR (if HTTP)\n- [ ] Mocking: Yes\u002FNo — doubles \u002F stubs \u002F VCR cassettes\n- [ ] Edge cases covered: \u003Clist>\n\n#### Generated Test Code\n\u003Cfile_path>\n\u003Ccode>\n\n#### Running Commands\n```bash\nbundle exec rspec spec\u002Fxxx_spec.rb\nbundle exec rspec --tag ~slow\nbundle exec rspec --only-failures\n```\n\n#### Coverage Impact (if applicable)\n- Estimated coverage change: \u003CX%>\n- Command to verify: `bundle exec rspec` (with SimpleCov configured in spec_helper.rb)\n```\n\n### Phase 5: Verification Checklist\nBefore completing, verify:\n- [ ] Tests run without parse errors (`bundle exec rspec`).\n- [ ] Each `it` block has exactly one logical assertion focus.\n- [ ] Mocks are reset between tests (`verify_partial_doubles` enabled).\n- [ ] No `sleep` used for synchronization (use `Timecop` or Capybara waiters).\n- [ ] Flaky test guardrails applied (`DatabaseCleaner`, `--order random`, deterministic factories).\n\n## TDD Workflow\n\n```ruby\n# spec\u002Fadd_spec.rb\ndef add(a, b) = nil # stub\n\nRSpec.describe 'add' do\n  it 'adds two numbers' do # RED\n    expect(add(2, 3)).to eq(5)\n  end\nend\n\n# lib\u002Fadd.rb\ndef add(a, b) = a + b # GREEN\n\n# REFACTOR: simplify\u002Frename once tests pass\n```\n\n## Code Examples\n\n### Basic Unit Test (RSpec)\n\n```ruby\n# spec\u002Fcalculator_spec.rb\nRSpec.describe Calculator do\n  describe '.add' do\n    it 'adds two numbers' do\n      expect(Calculator.add(2, 3)).to eq(5)\n    end\n\n    it 'handles negative numbers' do\n      expect(Calculator.add(-1, 1)).to eq(0)\n    end\n  end\nend\n```\n\n### Subject \u002F Let \u002F Before\n\n```ruby\n# spec\u002Fuser_store_spec.rb\nRSpec.describe UserStore do\n  subject(:store) { described_class.new }\n\n  before do\n    store.seed([User.new('alice'), User.new('bob')])\n  end\n\n  describe '#find' do\n    it 'returns existing user' do\n      user = store.find('alice')\n      expect(user).not_to be_nil\n      expect(user.name).to eq('alice')\n    end\n\n    it 'returns nil for missing user' do\n      expect(store.find('charlie')).to be_nil\n    end\n  end\nend\n```\n\n### Mocking (RSpec Mocks)\n\n```ruby\n# spec\u002Fservice_spec.rb\nRSpec.describe Service do\n  it 'sends notifications' do\n    notifier = double('Notifier', send: nil)\n    service = described_class.new(notifier)\n\n    service.publish('hello')\n\n    expect(notifier).to have_received(:send).with('hello').once\n  end\nend\n```\n\n### Stubs and Message Expectations\n\n```ruby\nRSpec.describe PaymentService do\n  it 'charges the correct amount' do\n    gateway = instance_double('PaymentGateway')\n    allow(gateway).to receive(:charge).with(100.0).and_return(true)\n\n    service = described_class.new(gateway)\n    result = service.process_order(100.0)\n\n    expect(result).to be true\n    expect(gateway).to have_received(:charge).with(100.0)\n  end\nend\n```\n\n### Parametrized Tests\n\n> **Requires:** `gem 'rspec-parameterized'` in your Gemfile.\n\n```ruby\n# spec\u002Fmath_spec.rb\nRSpec.describe Math do\n  describe '.add' do\n    where(:a, :b, :expected) do\n      [\n        [1, 2, 3],\n        [-1, 1, 0],\n        [0, 0, 0],\n        [100, 200, 300],\n      ]\n    end\n\n    with_them do\n      it 'returns the sum' do\n        expect(described_class.add(a, b)).to eq(expected)\n      end\n    end\n  end\nend\n```\n\n### Shared Examples\n\n```ruby\nRSpec.shared_examples 'a notifier' do\n  it 'responds to #send' do\n    expect(subject).to respond_to(:send)\n  end\nend\n\nRSpec.describe EmailNotifier do\n  it_behaves_like 'a notifier'\nend\n\nRSpec.describe SmsNotifier do\n  it_behaves_like 'a notifier'\nend\n```\n\n### Exception Testing\n\n```ruby\nRSpec.describe Validator do\n  describe '.validate_age' do\n    it 'raises on negative age' do\n      expect { described_class.validate_age(-1) }\n        .to raise_error(ArgumentError, \u002Fnon-negative\u002F)\n    end\n  end\nend\n```\n\n### Controller Test (Rails)\n\n```ruby\n# spec\u002Frequests\u002Fusers_spec.rb\nRSpec.describe 'Users API', type: :request do\n  describe 'GET \u002Fusers\u002F:id' do\n    it 'returns the user' do\n      user = User.create!(name: 'alice')\n\n      get \"\u002Fusers\u002F#{user.id}\"\n\n      expect(response).to have_http_status(:ok)\n      expect(JSON.parse(response.body)['name']).to eq('alice')\n    end\n  end\nend\n```\n\n## Gemfile Quickstart\n\n```ruby\n# Gemfile\ngroup :test do\n  gem 'rspec', '~> 3.13'\n  gem 'rspec-rails', '~> 7.0'  # if Rails\n  gem 'factory_bot_rails', '~> 6.0' # if Rails\n  gem 'faker', '~> 3.0'        # test data generation\n  gem 'shoulda-matchers', '~> 6.0' # if Rails, for model matchers\n  gem 'database_cleaner-active_record', '~> 2.0' # if Rails\n  gem 'simplecov', '~> 0.22', require: false\n  gem 'vcr', '~> 6.0'                        # HTTP recording\nend\n```\n\n## RSpec Configuration\n\n```ruby\n# spec\u002Fspec_helper.rb\nrequire 'simplecov'\nSimpleCov.start\n\nRSpec.configure do |config|\n  config.expect_with :rspec do |expectations|\n    expectations.include_chain_clauses_in_custom_matcher_descriptions = true\n  end\n\n  config.mock_with :rspec do |mocks|\n    mocks.verify_partial_doubles = true\n  end\n\n  config.shared_context_metadata_behavior = :apply_to_host_groups\n  config.filter_run_when_matching :focus\n  config.example_status_persistence_file_path = 'spec\u002Fexamples.txt'\n  config.disable_monkey_patching!\n  config.profile_examples = 10\n  config.order = :random\n  Kernel.srand config.seed\nend\n```\n\n## Running Tests\n\n```bash\nbundle exec rspec                         # all specs\nbundle exec rspec spec\u002Fcalculator_spec.rb  # single file\nbundle exec rspec spec\u002Fcalculator_spec.rb:5  # line number\nbundle exec rspec --tag slow              # tagged tests\nbundle exec rspec --tag ~slow             # exclude slow\nbundle exec rspec --only-failures          # rerun failures\nbundle exec rspec --next-failure           # one failure at a time\nbundle exec rspec --format documentation   # verbose\n```\n\n## Coverage\n\n```ruby\n# spec\u002Fspec_helper.rb (top)\nrequire 'simplecov'\nSimpleCov.start 'rails' do # or 'rails' adapter for Rails\n  minimum_coverage 90\n  maximum_coverage_drop 5\n  add_filter '\u002Fspec\u002F'\n  add_filter '\u002Fconfig\u002F'\nend\n```\n\n```bash\nbundle exec rspec\nopen coverage\u002Findex.html   # view HTML report\n```\n\n## Debugging Failures\n\n1. Re-run with `--format documentation` for full output.\n2. Use `--only-failures` to focus on failing tests.\n3. Add `require 'pry'; binding.pry` in test or code.\n4. Use `--seed \u003Cseed>` to reproduce randomized order.\n5. Expand to full suite once the root cause is fixed.\n\n## CI\u002FCD Integration (GitHub Actions)\n\n```yaml\n# .github\u002Fworkflows\u002Ftest.yml\nname: Test\non: [push, pull_request]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: ruby\u002Fsetup-ruby@v1\n        with: { ruby-version: '3.3' }\n      - run: bundle install\n      - run: bundle exec rspec --format progress --format RspecJunitFormatter --out report.xml\n      - uses: actions\u002Fupload-artifact@v4\n        if: always()\n        with: { name: test-results, path: report.xml }\n```\n\n## Flaky Tests Guardrails\n\n- Never use `sleep(n)` for synchronization; use `Capybara` waiters or `have_selector`.\n- Use `DatabaseCleaner` with truncation for test DB isolation.\n- Use `Faker::UniqueGenerator` for unique test data.\n- Avoid `Time.now` — use `Timecop.freeze` or `travel_to`.\n- Run with `--order random` to surface ordering dependencies.\n\n## Test Case Design Patterns\n\nApply these systematic patterns before writing test code to ensure robust coverage beyond the happy path.\n\n### 1. Boundary Value Analysis\nTest at the edges of valid input domains: empty, zero, maximum, nil, and just-outside valid ranges.\n\n```ruby\n# spec\u002Fvalidator_spec.rb\nRSpec.describe Validator do\n  describe '.validate' do\n    ['', ' ', 'a' * 10001].each do |input|\n      it \"rejects #{input.inspect}\" do\n        expect { described_class.validate(input) }.to raise_error(ArgumentError)\n      end\n    end\n  end\nend\n```\n\n### 2. Negative \u002F Error Case Testing\nVerify the system behaves correctly under invalid or unexpected conditions.\n\n```ruby\n# spec\u002Fcalculator_spec.rb\nRSpec.describe Calculator do\n  describe '.divide' do\n    it 'raises on divide by zero' do\n      expect { described_class.divide(10, 0) }.to raise_error(ZeroDivisionError)\n    end\n  end\nend\n```\n\n### 3. State-Based Testing\nFor objects with internal state, verify transitions between states are correct and irreversible where expected.\n\n```ruby\n# spec\u002Fconnection_spec.rb\nRSpec.describe Connection do\n  describe '#state' do\n    it 'transitions through states' do\n      conn = described_class.new\n      expect(conn.state).to eq(:closed)\n      conn.open\n      expect(conn.state).to eq(:open)\n      conn.close\n      expect(conn.state).to eq(:closed)\n      # Idempotency\n      conn.close\n      expect(conn.state).to eq(:closed)\n    end\n  end\nend\n```\n\n## Common Test Smells to Avoid\n\n- **Assertion Roulette**: Many assertions without descriptive messages, making failure diagnosis ambiguous. *Fix: Use `expect(actual).to eq(expected)` with descriptive `describe`\u002F`context`\u002F`it` nesting.*\n- **Happy Path Bias**: Only testing successful scenarios, leaving error paths uncovered. *Fix: Test `nil` values, `expect { ... }.to raise_error(...)`, edge conditions.*\n- **Mock Overdose**: Too many doubles hiding integration issues or coupling tests to implementation. *Fix: Use `instance_double` over `double`; mock only at service boundaries.*\n- **The Free Ride**: Tests that execute code but assert nothing meaningful. *Fix: Every test must have at least one specific, falsifiable expectation.*\n- **Obscure Test**: Long `it` blocks with no clear setup\u002Fverify structure. *Fix: Follow AAA, use `let`\u002F`let!` for lazy setup, keep expectations focused.*\n- **Flaky Test**: Non-deterministic results from DB state, time, or external services. *Fix: Use `DatabaseCleaner`, `travel_to`, `VCR.use_cassette`, `--order random`.*\n\n## Best Practices\n\n### DO\n\n- Use `let` and `subject` for lazy-evaluated test values\n- Use `described_class` instead of hardcoded class names\n- Keep specs focused — one `it` block per behavior\n- Use `context` blocks for different scenarios\n- Use `factory_bot` for test data (Rails)\n- Use `shoulda-matchers` for concise model validations\u002Fassociations\n\n### DON'T\n\n- Don't use `sleep` for synchronization\n- Don't use `before(:all)` for mutable state (leaks between tests)\n- Don't write overly nested `context` blocks\n- Don't use `should` syntax (old style) — use `expect`\n- Don't use `subject` when a named `let` is clearer\n- Don't test private methods directly\n\n### Common Pitfalls\n\n- **Flaky DB state** → Use `DatabaseCleaner` with truncation for non-transactional tests.\n- **Time-dependent tests** → Use `Timecop` or `travel_to` (Rails).\n- **Order-dependent tests** → Use `--order random` with `--seed` in CI.\n- **Slow Capybara tests** → Use `:js` tag only when needed; use `:rack_test` as default.\n- **Over-mocking** → Prefer real objects for core domain logic.\n- **Stale snapshots** → Use `approvals` gem carefully; review snapshot changes in PR.\n- **Missing coverage gates** → Add `SimpleCov.minimum_coverage` in CI.\n\n## Optional Appendix: Property-Based Testing\n\n```ruby\n# Using rspec_eventually or rspec-given\nrequire 'rantly\u002Frspec_extensions'\n\nRSpec.describe 'add property' do\n  it 'is commutative' do\n    property_of {\n      [integer, integer]\n    }.check { |a, b|\n      expect(Math.add(a, b)).to eq(Math.add(b, a))\n    }\n  end\nend\n```\n\n## Alternatives\n\n- **Minitest**: Ruby's built-in test framework, simpler, faster.\n- **Test::Unit**: Classic xUnit style, bundled with Ruby, compatible with RSpec runner.\n- **Cucumber**: Gherkin-based BDD for acceptance tests.\n","# Ruby Testing (Agent Skill)\n\nAgent-focused testing workflow for modern Ruby (3.2+) using RSpec, the dominant BDD-style test framework for Ruby.\n\n## When to Use\n\n- Writing new Ruby tests or fixing existing tests\n- Designing unit\u002Fintegration test coverage for Ruby components\n- Adding test coverage, CI gating, or regression protection\n- Configuring RSpec workflows for consistent execution\n- Investigating test failures or flaky behavior\n- Testing Rails apps, gems, or Sinatra services\n\n### When NOT to Use\n\n- Implementing new product features without test changes\n- Large-scale refactors unrelated to test coverage or failures\n- Performance tuning without test regressions to validate\n- Non-Ruby projects or non-test tasks\n\n### Stop Conditions — When to ASK instead of generating\n\nBefore generating any test code, STOP and ask the user if:\n- The code under test has **zero testability** (monkey-patched classes, hard-coded HTTP, no dependency injection)\n- **No test framework** is detected (no rspec\u002Fminitest in Gemfile, no spec\u002F directory)\n- The request is **ambiguous**: \"test my app\" or \"add tests\" without specifying files or classes\n- A **required test dependency** (rspec, rspec-rails, factory_bot) is not installed\n- The test would require **secrets, real credentials, or production database access**\n\n## Core Concepts\n\n- **TDD loop**: red → green → refactor (tests first, minimal fix, then cleanups).\n- **BDD style**: `describe` \u002F `context` \u002F `it` blocks for readable specifications.\n- **Test layout**: `spec\u002F` directory mirroring `lib\u002F` structure, `spec\u002Fspec_helper.rb`.\n- **Mocks**: RSpec's built-in `double()`, `allow()`, `expect()`.\n- **Factory pattern**: `factory_bot` for test data creation.\n- **CI signal**: `bundle exec rspec --format progress --color`.\n\n## Agent Execution Protocol\n\nWhen this skill is activated, follow this exact execution sequence:\n\n### Phase 1: Context Analysis\nBefore writing any test code:\n1. Identify the Ruby version (3.2+) and framework (RSpec, Minitest).\n2. Check for existing test files (`spec\u002F`, `test\u002F`) to infer naming conventions and style.\n3. Analyze the code under test: identify public API surface, side effects, ActiveRecord interactions, and async patterns.\n4. Determine request type: (a) new tests, (b) fixing broken tests, (c) coverage improvement, (d) flaky test diagnosis.\n\n### Phase 2: Strategy Selection\nBased on Phase 1, select the appropriate strategy:\n- **Pure functions \u002F utilities**: unit tests only, no mocks. Use `describe`\u002F`it` with `expect`.\n- **External dependencies (DB, HTTP, filesystem)**: use RSpec doubles\u002Fmocks; prefer `factory_bot` for data, `VCR` for HTTP recording, `DatabaseCleaner` for DB isolation.\n- **Rails applications**: use `type: :request` for API tests, `type: :model` for model tests; use `travel_to` for time freezing.\n- **Async code**: Ruby is mostly synchronous; use `have_received` for mock verification.\n\n### Decision Tree (Complex Scenarios)\n\n```text\nQ1: External I\u002FO (DB, HTTP, filesystem)?\n  → Yes → Q2\n  → No  → Pure unit test, no mocks needed\n\nQ2: Mockable (DI, service object, HTTP client)?\n  → Yes → Mock at boundary (RSpec doubles, VCR cassettes)\n  → No  → Q3\n\nQ3: Needs real DB behavior?\n  → Yes → DatabaseCleaner + factory_bot + in-memory\u002Ftest DB\n  → No  → Use simple double\u002Fstub\n\nQ4: Rails request\u002Fcontroller spec?\n  → Yes → type: :request with rspec-rails helpers\n  → No  → Q5\n\nQ5: Time-dependent code?\n  → Yes → travel_to \u002F Timecop for time freezing\n  → No  → Standard describe\u002Fit with expect()\n```\n\n### Phase 3: Test Case Design\nApply systematic test design before coding:\n1. **Happy path**: normal input → expected output\u002Fresponse.\n2. **Boundary values**: empty arrays, `nil`, empty strings, `0`, `Time.at(0)`.\n3. **Error cases**: exceptions (`raise_error`), validation errors, 4xx\u002F5xx responses, nil pointer scenarios.\n4. **State & database**: shared mutable state, `let` vs `let!` evaluation order, transaction isolation.\n\n### Phase 4: Code Generation\nOutput tests in this exact structure:\n```\n#### Test Strategy Summary\n- [ ] Scope: Unit \u002F Request \u002F Feature \u002F System\n- [ ] Framework: RSpec + factory_bot + VCR (if HTTP)\n- [ ] Mocking: Yes\u002FNo — doubles \u002F stubs \u002F VCR cassettes\n- [ ] Edge cases covered: \u003Clist>\n\n#### Generated Test Code\n\u003Cfile_path>\n\u003Ccode>\n\n#### Running Commands\n```bash\nbundle exec rspec spec\u002Fxxx_spec.rb\nbundle exec rspec --tag ~slow\nbundle exec rspec --only-failures\n```\n\n#### Coverage Impact (if applicable)\n- Estimated coverage change: \u003CX%>\n- Command to verify: `bundle exec rspec` (with SimpleCov configured in spec_helper.rb)\n```\n\n### Phase 5: Verification Checklist\nBefore completing, verify:\n- [ ] Tests run without parse errors (`bundle exec rspec`).\n- [ ] Each `it` block has exactly one logical assertion focus.\n- [ ] Mocks are reset between tests (`verify_partial_doubles` enabled).\n- [ ] No `sleep` used for synchronization (use `Timecop` or Capybara waiters).\n- [ ] Flaky test guardrails applied (`DatabaseCleaner`, `--order random`, deterministic factories).\n\n## TDD Workflow\n\n```ruby\n# spec\u002Fadd_spec.rb\ndef add(a, b) = nil # stub\n\nRSpec.describe 'add' do\n  it 'adds two numbers' do # RED\n    expect(add(2, 3)).to eq(5)\n  end\nend\n\n# lib\u002Fadd.rb\ndef add(a, b) = a + b # GREEN\n\n# REFACTOR: simplify\u002Frename once tests pass\n```\n\n## Code Examples\n\n### Basic Unit Test (RSpec)\n\n```ruby\n# spec\u002Fcalculator_spec.rb\nRSpec.describe Calculator do\n  describe '.add' do\n    it 'adds two numbers' do\n      expect(Calculator.add(2, 3)).to eq(5)\n    end\n\n    it 'handles negative numbers' do\n      expect(Calculator.add(-1, 1)).to eq(0)\n    end\n  end\nend\n```\n\n### Subject \u002F Let \u002F Before\n\n```ruby\n# spec\u002Fuser_store_spec.rb\nRSpec.describe UserStore do\n  subject(:store) { described_class.new }\n\n  before do\n    store.seed([User.new('alice'), User.new('bob')])\n  end\n\n  describe '#find' do\n    it 'returns existing user' do\n      user = store.find('alice')\n      expect(user).not_to be_nil\n      expect(user.name).to eq('alice')\n    end\n\n    it 'returns nil for missing user' do\n      expect(store.find('charlie')).to be_nil\n    end\n  end\nend\n```\n\n### Mocking (RSpec Mocks)\n\n```ruby\n# spec\u002Fservice_spec.rb\nRSpec.describe Service do\n  it 'sends notifications' do\n    notifier = double('Notifier', send: nil)\n    service = described_class.new(notifier)\n\n    service.publish('hello')\n\n    expect(notifier).to have_received(:send).with('hello').once\n  end\nend\n```\n\n### Stubs and Message Expectations\n\n```ruby\nRSpec.describe PaymentService do\n  it 'charges the correct amount' do\n    gateway = instance_double('PaymentGateway')\n    allow(gateway).to receive(:charge).with(100.0).and_return(true)\n\n    service = described_class.new(gateway)\n    result = service.process_order(100.0)\n\n    expect(result).to be true\n    expect(gateway).to have_received(:charge).with(100.0)\n  end\nend\n```\n\n### Parametrized Tests\n\n> **Requires:** `gem 'rspec-parameterized'` in your Gemfile.\n\n```ruby\n# spec\u002Fmath_spec.rb\nRSpec.describe Math do\n  describe '.add' do\n    where(:a, :b, :expected) do\n      [\n        [1, 2, 3],\n        [-1, 1, 0],\n        [0, 0, 0],\n        [100, 200, 300],\n      ]\n    end\n\n    with_them do\n      it 'returns the sum' do\n        expect(described_class.add(a, b)).to eq(expected)\n      end\n    end\n  end\nend\n```\n\n### Shared Examples\n\n```ruby\nRSpec.shared_examples 'a notifier' do\n  it 'responds to #send' do\n    expect(subject).to respond_to(:send)\n  end\nend\n\nRSpec.describe EmailNotifier do\n  it_behaves_like 'a notifier'\nend\n\nRSpec.describe SmsNotifier do\n  it_behaves_like 'a notifier'\nend\n```\n\n### Exception Testing\n\n```ruby\nRSpec.describe Validator do\n  describe '.validate_age' do\n    it 'raises on negative age' do\n      expect { described_class.validate_age(-1) }\n        .to raise_error(ArgumentError, \u002Fnon-negative\u002F)\n    end\n  end\nend\n```\n\n### Controller Test (Rails)\n\n```ruby\n# spec\u002Frequests\u002Fusers_spec.rb\nRSpec.describe 'Users API', type: :request do\n  describe 'GET \u002Fusers\u002F:id' do\n    it 'returns the user' do\n      user = User.create!(name: 'alice')\n\n      get \"\u002Fusers\u002F#{user.id}\"\n\n      expect(response).to have_http_status(:ok)\n      expect(JSON.parse(response.body)['name']).to eq('alice')\n    end\n  end\nend\n```\n\n## Gemfile Quickstart\n\n```ruby\n# Gemfile\ngroup :test do\n  gem 'rspec', '~> 3.13'\n  gem 'rspec-rails', '~> 7.0'  # if Rails\n  gem 'factory_bot_rails', '~> 6.0' # if Rails\n  gem 'faker', '~> 3.0'        # test data generation\n  gem 'shoulda-matchers', '~> 6.0' # if Rails, for model matchers\n  gem 'database_cleaner-active_record', '~> 2.0' # if Rails\n  gem 'simplecov', '~> 0.22', require: false\n  gem 'vcr', '~> 6.0'                        # HTTP recording\nend\n```\n\n## RSpec Configuration\n\n```ruby\n# spec\u002Fspec_helper.rb\nrequire 'simplecov'\nSimpleCov.start\n\nRSpec.configure do |config|\n  config.expect_with :rspec do |expectations|\n    expectations.include_chain_clauses_in_custom_matcher_descriptions = true\n  end\n\n  config.mock_with :rspec do |mocks|\n    mocks.verify_partial_doubles = true\n  end\n\n  config.shared_context_metadata_behavior = :apply_to_host_groups\n  config.filter_run_when_matching :focus\n  config.example_status_persistence_file_path = 'spec\u002Fexamples.txt'\n  config.disable_monkey_patching!\n  config.profile_examples = 10\n  config.order = :random\n  Kernel.srand config.seed\nend\n```\n\n## Running Tests\n\n```bash\nbundle exec rspec                         # all specs\nbundle exec rspec spec\u002Fcalculator_spec.rb  # single file\nbundle exec rspec spec\u002Fcalculator_spec.rb:5  # line number\nbundle exec rspec --tag slow              # tagged tests\nbundle exec rspec --tag ~slow             # exclude slow\nbundle exec rspec --only-failures          # rerun failures\nbundle exec rspec --next-failure           # one failure at a time\nbundle exec rspec --format documentation   # verbose\n```\n\n## Coverage\n\n```ruby\n# spec\u002Fspec_helper.rb (top)\nrequire 'simplecov'\nSimpleCov.start 'rails' do # or 'rails' adapter for Rails\n  minimum_coverage 90\n  maximum_coverage_drop 5\n  add_filter '\u002Fspec\u002F'\n  add_filter '\u002Fconfig\u002F'\nend\n```\n\n```bash\nbundle exec rspec\nopen coverage\u002Findex.html   # view HTML report\n```\n\n## Debugging Failures\n\n1. Re-run with `--format documentation` for full output.\n2. Use `--only-failures` to focus on failing tests.\n3. Add `require 'pry'; binding.pry` in test or code.\n4. Use `--seed \u003Cseed>` to reproduce randomized order.\n5. Expand to full suite once the root cause is fixed.\n\n## CI\u002FCD Integration (GitHub Actions)\n\n```yaml\n# .github\u002Fworkflows\u002Ftest.yml\nname: Test\non: [push, pull_request]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: ruby\u002Fsetup-ruby@v1\n        with: { ruby-version: '3.3' }\n      - run: bundle install\n      - run: bundle exec rspec --format progress --format RspecJunitFormatter --out report.xml\n      - uses: actions\u002Fupload-artifact@v4\n        if: always()\n        with: { name: test-results, path: report.xml }\n```\n\n## Flaky Tests Guardrails\n\n- Never use `sleep(n)` for synchronization; use `Capybara` waiters or `have_selector`.\n- Use `DatabaseCleaner` with truncation for test DB isolation.\n- Use `Faker::UniqueGenerator` for unique test data.\n- Avoid `Time.now` — use `Timecop.freeze` or `travel_to`.\n- Run with `--order random` to surface ordering dependencies.\n\n## Test Case Design Patterns\n\nApply these systematic patterns before writing test code to ensure robust coverage beyond the happy path.\n\n### 1. Boundary Value Analysis\nTest at the edges of valid input domains: empty, zero, maximum, nil, and just-outside valid ranges.\n\n```ruby\n# spec\u002Fvalidator_spec.rb\nRSpec.describe Validator do\n  describe '.validate' do\n    ['', ' ', 'a' * 10001].each do |input|\n      it \"rejects #{input.inspect}\" do\n        expect { described_class.validate(input) }.to raise_error(ArgumentError)\n      end\n    end\n  end\nend\n```\n\n### 2. Negative \u002F Error Case Testing\nVerify the system behaves correctly under invalid or unexpected conditions.\n\n```ruby\n# spec\u002Fcalculator_spec.rb\nRSpec.describe Calculator do\n  describe '.divide' do\n    it 'raises on divide by zero' do\n      expect { described_class.divide(10, 0) }.to raise_error(ZeroDivisionError)\n    end\n  end\nend\n```\n\n### 3. State-Based Testing\nFor objects with internal state, verify transitions between states are correct and irreversible where expected.\n\n```ruby\n# spec\u002Fconnection_spec.rb\nRSpec.describe Connection do\n  describe '#state' do\n    it 'transitions through states' do\n      conn = described_class.new\n      expect(conn.state).to eq(:closed)\n      conn.open\n      expect(conn.state).to eq(:open)\n      conn.close\n      expect(conn.state).to eq(:closed)\n      # Idempotency\n      conn.close\n      expect(conn.state).to eq(:closed)\n    end\n  end\nend\n```\n\n## Common Test Smells to Avoid\n\n- **Assertion Roulette**: Many assertions without descriptive messages, making failure diagnosis ambiguous. *Fix: Use `expect(actual).to eq(expected)` with descriptive `describe`\u002F`context`\u002F`it` nesting.*\n- **Happy Path Bias**: Only testing successful scenarios, leaving error paths uncovered. *Fix: Test `nil` values, `expect { ... }.to raise_error(...)`, edge conditions.*\n- **Mock Overdose**: Too many doubles hiding integration issues or coupling tests to implementation. *Fix: Use `instance_double` over `double`; mock only at service boundaries.*\n- **The Free Ride**: Tests that execute code but assert nothing meaningful. *Fix: Every test must have at least one specific, falsifiable expectation.*\n- **Obscure Test**: Long `it` blocks with no clear setup\u002Fverify structure. *Fix: Follow AAA, use `let`\u002F`let!` for lazy setup, keep expectations focused.*\n- **Flaky Test**: Non-deterministic results from DB state, time, or external services. *Fix: Use `DatabaseCleaner`, `travel_to`, `VCR.use_cassette`, `--order random`.*\n\n## Best Practices\n\n### DO\n\n- Use `let` and `subject` for lazy-evaluated test values\n- Use `described_class` instead of hardcoded class names\n- Keep specs focused — one `it` block per behavior\n- Use `context` blocks for different scenarios\n- Use `factory_bot` for test data (Rails)\n- Use `shoulda-matchers` for concise model validations\u002Fassociations\n\n### DON'T\n\n- Don't use `sleep` for synchronization\n- Don't use `before(:all)` for mutable state (leaks between tests)\n- Don't write overly nested `context` blocks\n- Don't use `should` syntax (old style) — use `expect`\n- Don't use `subject` when a named `let` is clearer\n- Don't test private methods directly\n\n### Common Pitfalls\n\n- **Flaky DB state** → Use `DatabaseCleaner` with truncation for non-transactional tests.\n- **Time-dependent tests** → Use `Timecop` or `travel_to` (Rails).\n- **Order-dependent tests** → Use `--order random` with `--seed` in CI.\n- **Slow Capybara tests** → Use `:js` tag only when needed; use `:rack_test` as default.\n- **Over-mocking** → Prefer real objects for core domain logic.\n- **Stale snapshots** → Use `approvals` gem carefully; review snapshot changes in PR.\n- **Missing coverage gates** → Add `SimpleCov.minimum_coverage` in CI.\n\n## Optional Appendix: Property-Based Testing\n\n```ruby\n# Using rspec_eventually or rspec-given\nrequire 'rantly\u002Frspec_extensions'\n\nRSpec.describe 'add property' do\n  it 'is commutative' do\n    property_of {\n      [integer, integer]\n    }.check { |a, b|\n      expect(Math.add(a, b)).to eq(Math.add(b, a))\n    }\n  end\nend\n```\n\n## Alternatives\n\n- **Minitest**: Ruby's built-in test framework, simpler, faster.\n- **Test::Unit**: Classic xUnit style, bundled with Ruby, compatible with RSpec runner.\n- **Cucumber**: Gherkin-based BDD for acceptance tests.\n",{"name":5,"metadata":24,"description":27,"compatibility":28},{"author":25,"source":26},"github-spec-kit","templates\u002Fpassive-skills\u002Fruby-testing\u002FSKILL.md","Use ONLY when user explicitly requests Ruby test code, RSpec configuration, test failure diagnosis, or coverage improvement for Ruby projects.\nTriggers: \"write test ruby\", \"rspec\", \"rails test\", \"factory bot\", \"simplecov\", \"vcr test\", \"shared examples\", \"shoulda matchers\".\nDo NOT activate for: general Ruby coding without test context, gem\u002Fbundler issues, Rails model\u002Fcontroller implementation, or Ruby version management. ","Requires spec-kit project structure with .specify\u002F directory","main","021f5cbe02157229460a577e9a585f2da6d78cb0",".codex\u002Fskills\u002Fruby-testing","https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck","https:\u002F\u002Fgithub.com\u002Fchenziyang110","2026-08-07 18:10:54",{"source":8,"skillName":5,"commands":36,"prompt":46,"zipAvailable":47},[37,40,43],{"tool":38,"command":39},"npx","npx skills add chenziyang110\u002Flaunchdeck --skill ruby-testing",{"tool":41,"command":42},"bunx","bunx skills add chenziyang110\u002Flaunchdeck --skill ruby-testing",{"tool":44,"command":45},"pnpm","pnpm dlx skills add chenziyang110\u002Flaunchdeck --skill ruby-testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck\u002Fblob\u002Fmain\u002F.codex\u002Fskills\u002Fruby-testing\u002FSKILL.md",true,[],[50,57,63,69,76,85],{"skillNo":51,"name":52,"nameZh":6,"description":53,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":54,"categories":55,"keywords":56,"publishedAt":19},"01M7875F3HQ1GF5F6BR36KY3JE","launchdeck-agent","Codex host adapter contract fixture for Launchdeck.","https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Fagent-hosts\u002Fcodex\u002Fskill\u002FSKILL.md",[],[],{"skillNo":58,"name":52,"nameZh":6,"description":59,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":60,"categories":61,"keywords":62,"publishedAt":19},"01M7875DV2DQR33K1NFJHKAMZP","Claude host adapter contract fixture for Launchdeck.","https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Fagent-hosts\u002Fclaude\u002Fskill\u002FSKILL.md",[],[],{"skillNo":64,"name":52,"nameZh":6,"description":65,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":66,"categories":67,"keywords":68,"publishedAt":19},"01M78759JQ7A6FQ8KDQTNYS3X9","Use this skill aggressively for local project lifecycle operations even when the user does not mention Launchdeck: start this project, run the dev server, dev, build, test, restart service, stop service, show logs, inspect ports, port is occupied, clean cache; and when the user explicitly asks to configure this local project for Launchdeck or create or generate a project-adapted `.launchdeck.yml`, including 为这个项目生成 Launchdeck 配置. Do not use for ordinary code edits, docs-only work, general explanations, API design discussion, or production deployment unless the user also asks to operate or configure a local project or service.","https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck\u002Fblob\u002Fmain\u002Fagent\u002Finstaller-payload\u002Fskill\u002Flaunchdeck-agent\u002FSKILL.md",[],[],{"skillNo":70,"name":71,"nameZh":6,"description":72,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":75,"publishedAt":19},"01M78753ZJQ7G2CZT1W1G8WRJV","zig-testing","Use ONLY when user explicitly requests Zig test code, built-in test runner configuration,\ntest failure diagnosis, or coverage for Zig projects.\n\nTriggers: \"write test zig\", \"zig test\", \"testing.allocator\", \"zig coverage\",\n\"comptime test\", \"zig expectError\", \"zig snapshot test\", \"zig fuzz\".\n\nDo NOT activate for: general Zig coding without test context, build.zig configuration\nfor non-test targets, Zig compiler errors, or Zig build system setup.","https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck\u002Fblob\u002Fmain\u002F.specify\u002Ftemplates\u002Fpassive-skills\u002Fzig-testing\u002FSKILL.md",[],[],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":80,"licenseFileUrl":81,"sourceHtmlUrl":82,"categories":83,"keywords":84,"publishedAt":19},"01M78750JXY4Z5D5W1EX3PQ31Z","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck\u002Fblob\u002Fmain\u002F.specify\u002Ftemplates\u002Fpassive-skills\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck\u002Fblob\u002Fmain\u002F.specify\u002Ftemplates\u002Fpassive-skills\u002Fwebapp-testing\u002FSKILL.md",[],[],{"skillNo":86,"name":87,"nameZh":6,"description":88,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":89,"categories":90,"keywords":91,"publishedAt":19},"01M7874X2T83EQQFRCEY5SJJVC","verification-before-completion","Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always","https:\u002F\u002Fgithub.com\u002Fchenziyang110\u002Flaunchdeck\u002Fblob\u002Fmain\u002F.specify\u002Ftemplates\u002Fpassive-skills\u002Fverification-before-completion\u002FSKILL.md",[],[]]