agentsclimarketplace

Rails testing

Skill Tyr0/agent-skills/plugins/rails-expert/skills/rails-testing

Use this skill whenever the user asks about testing in Ruby on Rails, including setting up Minitest, writing model tests, integration tests, system tests, mailer tests, or job tests. Also use it for questions about fixtures, test helpers, SimpleCov coverage, Capybara system tests, parallel test execution, or RuboCop-Minitest style enforcement. Triggers on 'how do I write a test', 'what is a fixture', 'how do I test a controller', 'how do I set up SimpleCov', 'how do I run system tests', or any question about the test/ directory structure.From its SKILL.md

Install
npx -y skills add Tyr0/agent-skills --skill rails-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

12.5 KB, ~3.0k tokens by cl100k_base, as published. Nobody here has run it

Rails Testing Reference (Minitest)

A dense reference for Rails testing with Minitest — the framework built into Rails. Covers setup, fixtures, all test types, coverage, and anti-patterns.

Framework: Minitest (Rails default). No additional gems required for basic testing.


Test Directory Structure

test/
├── controllers/          # ActionDispatch::IntegrationTest (preferred over functional tests)
├── fixtures/             # YAML fixture files
├── helpers/              # Custom test helper modules (auto-loaded via test_helper.rb)
├── integration/          # Multi-step request flows (ActionDispatch::IntegrationTest)
├── mailers/              # ActionMailer::TestCase
├── models/               # ActiveSupport::TestCase
├── system/               # ActionDispatch::SystemTestCase (Capybara)
├── validators/           # ActiveSupport::TestCase for custom validators
├── application_system_test_case.rb
└── test_helper.rb

Test Helper Setup

# test/test_helper.rb
require "simplecov"
SimpleCov.start "rails" do
  add_group "Validators", "app/validators"
  enable_coverage :branch
  primary_coverage :branch
end

ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"

Rails.application.eager_load!

# Auto-load all test helpers
Dir[Rails.root.join("test", "helpers", "**", "*.rb")].each { |file| require file }

module ActiveSupport
  class TestCase
    parallelize(workers: :number_of_processors)

    parallelize_setup do |worker|
      SimpleCov.command_name "#{SimpleCov.command_name}-#{worker}"
    end

    parallelize_teardown do |worker|
      SimpleCov.result
    end

    fixtures :all
  end
end

Writing Tests

Test naming — use descriptive strings, not def test_

# Good
test "should require a valid email address" do
  # ...
end

# Avoid
def test_email_validation
  # ...
end

Core assertions

assert expr                       # expr is truthy
assert_not expr                   # expr is falsy (prefer over assert !)
assert_equal expected, actual     # == equality
assert_not_equal a, b
assert_nil value
assert_not_nil value
assert_includes collection, item
assert_empty collection
assert_raises(ErrorClass) { ... }
assert_difference "Model.count", 1 { ... }     # count changes by 1
assert_difference "Model.count", -1 { ... }    # count decreases by 1
assert_no_difference "Model.count" { ... }
assert_difference -> { A.count } => 1, -> { B.count } => -1 { ... }  # multiple

Model Tests

# test/models/user_test.rb
require "test_helper"

class UserTest < ActiveSupport::TestCase
  test "should be valid with valid attributes" do
    user = users(:john_doe)
    assert user.valid?
  end

  test "should require email" do
    user = users(:john_doe)
    user.email = nil
    assert_not user.valid?
    assert_includes user.errors[:email], "can't be blank"
  end

  test "should destroy associated posts on deletion" do
    user = users(:john_doe)
    assert_difference "Post.count", -user.posts.count do
      user.destroy!
    end
  end
end

Integration / Controller Tests

ActionDispatch::IntegrationTest is preferred for all request-level tests — it exercises the full stack including middleware.

# test/controllers/articles_controller_test.rb
require "test_helper"

class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "should get index" do
    get articles_path
    assert_response :success
  end

  test "should create article" do
    assert_difference "Article.count", 1 do
      post articles_path, params: { article: { title: "Hello", body: "World" } }
    end
    assert_redirected_to article_path(Article.last)
  end

  test "should return 404 for missing article" do
    get article_path(id: 99999)
    assert_response :not_found
  end
end

JSON API tests

class API::ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "should return articles as JSON" do
    get api_articles_path, as: :json
    assert_response :ok
    assert_equal "application/json", response.content_type.split(";").first

    body = response.parsed_body
    assert body.key?("articles")
  end

  test "should create article via API" do
    post api_articles_path,
      params: { article: { title: "Test" } },
      headers: { "Authorization" => "Bearer #{token}" },
      as: :json

    assert_response :created
  end
end

Authentication in integration tests

# Helper module for signing in
module AuthTestHelper
  def sign_in(user)
    post session_path, params: { email: user.email_address, password: "Password123!" }
  end
end

class ArticlesControllerTest < ActionDispatch::IntegrationTest
  include AuthTestHelper

  setup do
    sign_in users(:john_doe)
  end
end

Fixtures

YAML fixtures are the Rails-native way to seed test data. Loaded automatically with fixtures :all.

# test/fixtures/users.yml
john_doe:
  first_name: John
  last_name: Doe
  email_address: [email protected]
  password_digest: <%= BCrypt::Password.create("Password123!") %>
  admin: false

chuck_norris:
  first_name: Chuck
  last_name: Norris
  email_address: [email protected]
  password_digest: <%= BCrypt::Password.create("IDareYou!") %>
  admin: true
# test/fixtures/posts.yml
hello_world:
  title: Hello World
  user: john_doe       # references users.yml by label (FK resolved automatically)
  published: true
  published_at: <%= 1.day.ago.to_fs(:db) %>
# Access fixtures in tests
users(:john_doe)        # returns the User AR object
users(:john_doe).id     # resolved primary key

Fixture rules:

  • Use <%= %> for dynamic values (timestamps, digests).
  • Reference associations by label — Rails resolves the FK automatically.
  • Prefer a small, stable fixture set; add fixtures as you add features.
  • Do not compute expensive values inline — use BCrypt::Password.create only when auth matters.

System Tests (Capybara)

System tests drive a real browser via Selenium. Use for critical user flows.

# test/application_system_test_case.rb
require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end
# test/system/articles_test.rb
require "application_system_test_case"

class ArticlesTest < ApplicationSystemTestCase
  test "creating an article" do
    visit new_article_path

    fill_in "Title", with: "My Article"
    fill_in "Body", with: "Article content"
    click_on "Create Article"

    assert_text "My Article"
    assert_current_path article_path(Article.last)
  end
end

Mailer Tests

# test/mailers/user_mailer_test.rb
require "test_helper"

class UserMailerTest < ActionMailer::TestCase
  test "welcome email" do
    user = users(:john_doe)
    mail = UserMailer.welcome(user)

    assert_equal "Welcome to the app", mail.subject
    assert_equal [user.email_address], mail.to
    assert_match user.first_name, mail.body.encoded
  end

  test "welcome email is delivered" do
    user = users(:john_doe)
    assert_difference "ActionMailer::Base.deliveries.size", 1 do
      UserMailer.welcome(user).deliver_now
    end
  end
end

In integration tests, assert on deliveries:

assert_emails 1 do
  post users_path, params: { user: { email: "[email protected]" } }
end

Job Tests

# test/jobs/cleanup_job_test.rb
require "test_helper"

class CleanupJobTest < ActiveJob::TestCase
  test "should delete old records" do
    assert_difference "OldRecord.count", -1 do
      CleanupJob.perform_now
    end
  end

  test "should be enqueued on create" do
    assert_enqueued_with(job: WelcomeJob) do
      User.create!(email: "[email protected]", password: "Password1!")
    end
  end
end

Custom Validator Tests

Test validators in isolation with an inline test model to avoid coupling to AR lifecycle:

# test/validators/presence_validator_test.rb
require "test_helper"

class PresenceValidatorTest < ActiveSupport::TestCase
  class TestModel
    include ActiveModel::Validations
    attr_accessor :name
    validates :name, presence: true
    def initialize(name) = @name = name
  end

  test "should be invalid when name is blank" do
    model = TestModel.new("")
    assert_not model.valid?
    assert_includes model.errors[:name], "can't be blank"
  end

  test "should be valid when name is present" do
    model = TestModel.new("Alice")
    assert model.valid?
  end
end

Custom Test Helpers

Extract shared setup into modules in test/helpers/ (auto-loaded by test_helper.rb):

# test/helpers/api_test_helper.rb
module APITestHelper
  TEST_USER_AGENT = "TestAgent/1.0"

  def api_headers(token: nil)
    headers = { "User-Agent" => TEST_USER_AGENT, "Accept" => "application/json" }
    headers["Authorization"] = "Bearer #{token}" if token
    headers
  end
end

Include per-test-class:

class API::TokensControllerTest < ActionDispatch::IntegrationTest
  include APITestHelper
end

Parallel Tests

Enabled by default in the generated test_helper.rb:

parallelize(workers: :number_of_processors)
  • Each worker gets its own database: test-0, test-1, etc. (created automatically).
  • SimpleCov requires the parallel setup/teardown hooks to merge results (see test_helper.rb above).
  • Fixtures are loaded per-worker — no shared state between workers.

Code Coverage with SimpleCov

# Gemfile
group :test do
  gem "simplecov", require: false
end

Configure in test_helper.rb before loading the app:

require "simplecov"
SimpleCov.start "rails" do
  add_group "Validators", "app/validators"
  add_group "Services", "app/services"
  enable_coverage :branch     # track branch coverage (if/else, case)
  primary_coverage :branch    # report on branch coverage (not just line)
  minimum_coverage 90         # optional: fail CI if below threshold
end

Run: bin/rails test — SimpleCov generates coverage/index.html after the suite.


RuboCop-Minitest

# Gemfile
group :rubocop do
  gem "rubocop-minitest", require: false
end
# .rubocop.yml
plugins:
  - rubocop-minitest

Rails/AssertNot:
  Include:
    - "**/test/**/*"

Rails/RefuteMethods:
  Include:
    - "**/test/**/*"

Minitest/UnreachableAssertion:
  Enabled: true

Key enforced rules:

  • assert_not over assert !
  • assert_not_nil over refute_nil
  • No unreachable assertions after early returns

Running Tests

bin/rails test                             # run all tests
bin/rails test test/models/               # run a directory
bin/rails test test/models/user_test.rb   # run a single file
bin/rails test test/models/user_test.rb:8 # run a single test by line
bin/rails test:system                     # run system tests only
bin/rails test:all                        # models + integration + system

Anti-Patterns

Anti-patternProblemFix
Mocking the databaseDiverges from production behavior; misses real query bugsHit a real test DB; use transactions to roll back
No fixtures :allFixture helpers unavailableAdd fixtures :all to ActiveSupport::TestCase
User.create in tests instead of fixturesSlow; non-deterministic; fixture associations breakUse fixtures for stable data; create only when testing creation logic
Testing implementation details (private methods)Brittle; breaks on refactorTest behavior through the public interface
System tests for every scenarioSlow Selenium startup for simple logicReserve system tests for critical UI flows; use integration tests for the rest
No assertions in a test bodyTest always passes; silent breakageEvery test must have at least one assert_*
Calling deliver_now in tests without checking deliveryEmail bugs silently passUse assert_emails or check ActionMailer::Base.deliveries
Giant fixture filesHard to understand; slow to parseKeep fixtures minimal and focused; 2-4 records per model is usually enough

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,750. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.