Minitest Testing for Rails
Write comprehensive, maintainable Minitest tests following Rails conventions and best practices. This skill provides patterns for model tests, controller tests, system tests with Capybara, and fixture usage.
Core Testing Principles
- Test Structure
Organize tests with clear phases:
test "creates article with valid attributes" do
Arrange - set up test data
user = users(:alice)
Act - perform the action
article = Article.create( title: "Test Article", body: "Content here", user: user )
Assert - verify the outcome
assert article.persisted? assert_equal "Test Article", article.title end
- Test Independence
Each test should be independent and able to run in any order. Use setup for common initialization:
class ArticleTest < ActiveSupport::TestCase setup do @user = users(:alice) @article = articles(:published) end
test "publishes article" do @article.publish! assert @article.published? end end
- Single Responsibility
Each test verifies one behavior, though it may use multiple assertions to fully verify that behavior.
Test Types
Model Tests (test/models/ )
Test business logic, validations, associations, and custom methods.
File location: test/models/article_test.rb
class ArticleTest < ActiveSupport::TestCase test "validates presence of title" do article = Article.new(body: "Content") assert_not article.valid? assert_includes article.errors[:title], "can't be blank" end
test "belongs to user" do article = articles(:published) assert_instance_of User, article.user end
test "#published? returns true when status is published" do article = articles(:published) assert article.published? end end
Controller Tests (test/controllers/ )
Test HTTP responses, redirects, authentication, and parameter handling.
File location: test/controllers/articles_controller_test.rb
class ArticlesControllerTest < ActionDispatch::IntegrationTest setup do @user = users(:alice) sign_in_as(@user) # helper method end
test "GET index returns success" do get articles_path assert_response :success end
test "POST create with valid params creates article" do assert_difference("Article.count", 1) do post articles_path, params: { article: { title: "New Article", body: "Content" } } end
assert_redirected_to article_path(Article.last)
end
test "POST create with invalid params renders new" do assert_no_difference("Article.count") do post articles_path, params: { article: { title: "", body: "" } } end
assert_response :unprocessable_entity
end end
System Tests (test/system/ )
Test end-to-end user workflows with browser simulation.
File location: test/system/article_creation_test.rb
class ArticleCreationTest < ApplicationSystemTestCase test "user creates new article" do visit root_path click_on "New Article"
fill_in "Title", with: "My Test Article"
fill_in "Body", with: "This is the content"
select "Published", from: "Status"
click_on "Create Article"
assert_text "Article was successfully created"
assert_text "My Test Article"
end
test "article updates with Turbo Frame" do article = articles(:draft) visit article_path(article)
click_on "Edit"
fill_in "Title", with: "Updated Title"
click_on "Update Article"
# Turbo Frame update - no full page reload
assert_text "Updated Title"
assert_no_text article.title
end end
Detailed Patterns & Examples
For comprehensive Minitest patterns and examples, see examples.md.
Pattern Categories
examples.md contains:
-
Fixtures - YAML fixtures, associations, and fixture access
-
Model Testing - Validations, associations, scopes, callbacks, enums
-
Controller Testing - CRUD actions, authentication, authorization
-
System Testing - Capybara selectors, form interactions, JavaScript behavior
-
Turbo/Hotwire Testing - Turbo Frames, Turbo Streams, Stimulus controllers
-
Background Jobs - Testing ActiveJob and SolidQueue jobs
-
Mailers - Testing ActionMailer delivery and content
-
Test Helpers - Custom assertions and helper methods
-
Mocking/Stubbing - When and how to use mocks with Minitest
Refer to examples.md for complete code examples.
Common Assertions
Equality
assert_equal expected, actual assert_not_equal unexpected, actual
Truth/Falsehood
assert value assert_not value assert_nil value assert_not_nil value
Predicates
assert article.valid? assert_not article.persisted? assert articles.empty?
Collections
assert_includes array, item assert_empty collection assert_not_empty collection
Differences (counting changes)
assert_difference "Article.count", 1 do Article.create!(title: "New", body: "Content") end
assert_no_difference "Article.count" do Article.create(title: "", body: "") # invalid end
HTTP Responses
assert_response :success assert_response :redirect assert_response :not_found assert_redirected_to articles_path
Errors
assert_raises ActiveRecord::RecordInvalid do Article.create!(title: nil) end
Fixtures
Fixture Files (test/fixtures/*.yml )
test/fixtures/users.yml
alice: email: alice@example.com name: Alice Smith role: admin
bob: email: bob@example.com name: Bob Jones role: member
test/fixtures/articles.yml
published: user: alice title: Published Article body: This article is live status: published published_at: <%= 1.day.ago %>
draft: user: bob title: Draft Article body: Work in progress status: draft
Using Fixtures in Tests
test "finds published articles" do published = articles(:published) draft = articles(:draft)
results = Article.published
assert_includes results, published assert_not_includes results, draft end
Test Helpers
Authentication Helper
test/test_helper.rb
class ActionDispatch::IntegrationTest def sign_in_as(user) post login_path, params: { email: user.email, password: "password" } end end
Custom Assertions
test/test_helper.rb
module ActiveSupport class TestCase def assert_valid(record) assert record.valid?, "Expected #{record.class} to be valid, errors: #{record.errors.full_messages}" end end end
Running Tests
Run all tests
bin/rails test
Run specific test file
bin/rails test test/models/article_test.rb
Run specific test by line number
bin/rails test test/models/article_test.rb:12
Run tests by pattern
bin/rails test test/models/*_test.rb
Run system tests only
bin/rails test:system
Run tests in parallel
bin/rails test:parallel
Run with verbose output
bin/rails test -v
Run and show coverage
COVERAGE=true bin/rails test
Official Documentation:
-
Rails Testing Guide - Comprehensive Rails testing guide
-
Minitest Documentation - Minitest framework docs
-
Capybara Cheat Sheet - System test selectors
Tools:
-
SimpleCov - Code coverage for Ruby
-
shoulda-matchers - Additional matchers for Rails