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
test/fixtures/users.yml
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
Boolean
assert user.valid? assert_not user.admin?
Equality
assert_equal "Alice", user.name assert_nil user.deleted_at
Collections
assert_includes users, admin_user assert_empty order.items
Exceptions
assert_raises ActiveRecord::RecordInvalid do user.save! end
Rails Assertions
Changes
assert_changes -> { user.reload.status }, to: "active" do user.activate! end
assert_difference "User.count", 1 do User.create(name: "Charlie") end
Responses
assert_response :success assert_redirected_to user_path(user)
AAA Pattern
test "processes refund" do
Arrange
order = orders(:completed_order) original_balance = order.user.account_balance
Act
result = order.process_refund
Assert
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
Validations
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
Methods
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:
-
Files mirror app structure
-
NOT adding require 'test_helper'
-
Using fully qualified namespace
Style Choice:
-
Traditional for simple tests
-
Spec for complex contexts
-
Max 3 nesting levels
Test Data:
-
Using fixtures (not factories)
-
Using let for shared data
-
Using subject for method under test
Assertions:
-
Correct assertion methods
-
Rails helpers (assert_changes , assert_difference )
-
Testing behavior, not implementation
Coverage:
-
Happy path tested
-
Sad path tested
-
Edge cases covered
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.