| name | minitest-coder |
| description | Write Minitest tests for Ruby and Rails applications. Use when creating test files, writing test cases, or testing new features. Not for RSpec — use rspec-coder instead. Covers both traditional and spec styles, fixtures, mocking, and Rails integration testing patterns. |
| allowed-tools | Read Write Edit MultiEdit Grep Glob Bash WebSearch |
Minitest Coder
Core Philosophy
- AAA Pattern: Arrange-Act-Assert structure for clarity
- Behavior over Implementation: Test what code does, not how
- Isolation: Tests should be independent
- Descriptive Names: Clear test descriptions
- Coverage: Test happy paths AND edge cases
- Fast Tests: Minimize database operations
- Fixtures: Use fixtures for test data
Minitest Styles
| Style | Best For | Syntax |
|---|
| Traditional | Simple unit tests | test "description" |
| Spec | Complex scenarios with contexts | describe/it with let/subject |
Traditional Style
class UserTest < ActiveSupport::TestCase
test "validates presence of name" do
user = User.new
assert_not user.valid?
assert_includes user.errors[:name], "can't be blank"
end
end
Spec Style
class UserTest < ActiveSupport::TestCase
describe "#full_name" do
subject { user.full_name }
let(:user) { User.new(first_name: "Buffy", last_name:) }
describe "with last name" do
let(:last_name) { "Summers" }
it "returns full name" do
assert_equal "Buffy Summers", subject
end
end
end
end
Test Organization
File Structure
test/models/ - Model unit tests
test/services/ - Service object tests
test/integration/ - Full-stack tests
test/mailers/ - Mailer tests
test/jobs/ - Background job tests
test/fixtures/ - Test data
test/test_helper.rb - Configuration
Naming Conventions
- Mirror app structure:
app/models/user.rb → test/models/user_test.rb
- Use fully qualified namespace:
class Users::ProfileServiceTest
- Don't add
require 'test_helper' (auto-imported)
Spec Style Patterns
See references/spec-patterns.md for detailed examples.
| Pattern | Use Case |
|---|
subject { ... } | Method under test |
let(:name) { ... } | Lazy-evaluated data |
describe "context" | Group related tests (max 3 levels) |
before { ... } | Complex setup |
describe "#process" do
subject { processor.process }
let(:processor) { OrderProcessor.new(order) }
let(:order) { orders(:paid_order) }
it "succeeds" do
assert subject.success?
end
end
Fixtures
alice:
name: Alice Smith
email: alice@example.com
created_at: <%= 2.days.ago %>
class UserTest < ActiveSupport::TestCase
fixtures :users
test "validates uniqueness" do
duplicate = User.new(email: users(:alice).email)
assert_not duplicate.valid?
end
end
Mocking and Stubbing
See references/spec-patterns.md for detailed examples.
| Method | Purpose |
|---|
Object.stub :method, value | Stub return value |
Minitest::Mock.new | Verify method calls |
test "processes payment" do
PaymentGateway.stub :charge, true do
processor = OrderProcessor.new(order)
assert processor.process
end
end
Assertions Quick Reference
Basic Assertions
assert user.valid?
assert_not user.admin?
assert_equal "Alice", user.name
assert_nil user.deleted_at
assert_includes users, admin_user
assert_empty order.items
assert_raises ActiveRecord::RecordInvalid do
user.save!
end
Rails Assertions
assert_changes -> { user.reload.status }, to: "active" do
user.activate!
end
assert_difference "User.count", 1 do
User.create(name: "Charlie")
end
assert_response :success
assert_redirected_to user_path(user)
AAA Pattern
test "processes refund" do
order = orders(:completed_order)
original_balance = order.user.account_balance
result = order.process_refund
assert result.success?
assert_equal "refunded", order.reload.status
end
Spec style with subject:
describe "#process_refund" do
subject { order.process_refund }
let(:order) { orders(:completed_order) }
it "updates status" do
subject
assert_equal "refunded", order.reload.status
end
it "credits user" do
assert_changes -> { order.user.reload.account_balance }, by: order.total do
subject
end
end
end
Test Coverage Standards
| Type | Test For |
|---|
| Models | Validations, associations, scopes, callbacks, methods |
| Services | Happy path, sad path, edge cases, external integrations |
| Controllers | Status codes, redirects, parameter handling |
| Jobs | Execution, retry logic, error handling |
Coverage Example
class UserTest < ActiveSupport::TestCase
test "validates presence of name" do
user = User.new(email: "test@example.com")
assert_not user.valid?
assert_includes user.errors[:name], "can't be blank"
end
describe "#full_name" do
subject { user.full_name }
let(:user) { User.new(first_name: "Alice", last_name: "Smith") }
it "returns full name" do
assert_equal "Alice Smith", subject
end
describe "without last name" do
let(:user) { User.new(first_name: "Alice") }
it "returns first name only" do
assert_equal "Alice", subject
end
end
end
end
Advanced Patterns
See references/advanced-patterns.md for production-tested patterns from 37signals.
| Pattern | Problem Solved |
|---|
| Current.account fixtures | Multi-tenant URL isolation |
| Assertion-validating helpers | Early failure with clear messages |
| Deterministic UUIDs | Predictable fixture ordering |
| VCR timestamp filtering | Reusable API cassettes |
| Thread-based concurrency | Race condition detection |
| Adapter-aware helpers | SQLite/MySQL compatibility |
Anti-Patterns
See references/anti-patterns.md for detailed examples.
| Anti-Pattern | Why Bad |
|---|
require 'test_helper' | Auto-imported |
| >3 nesting levels | Unreadable output |
@ivars instead of let | State leakage |
Missing subject | Repetitive code |
assert x.include?(y) | Use assert_includes |
| Testing private methods | Implementation coupling |
| Not using fixtures | Slow tests |
Best Practices Checklist
Organization:
Style Choice:
Test Data:
Assertions:
Coverage:
When to Choose Style
Traditional: Simple validations, straightforward tests, no shared setup
Spec: Multiple contexts, lazy evaluation needed, nested scenarios, reusable subject
Can mix both in the same file if it improves clarity.