Introduction
Now that you understand the testing infrastructure, it is time to write your first test. Rails tests use Minitest, which provides a simple, readable syntax for defining test cases and verifying behavior with assertions. By the end of this lesson you will know how to create a test file, write test methods, and use setup hooks to keep your tests clean.
Key Concepts
- Test Case Class: A Ruby class that inherits from
ActiveSupport::TestCase(for models) or a more specific base class. Each public method defined with thetestmacro becomes a runnable test. - Assertion: A method call that verifies an expected condition. If the condition is false, the test fails with a descriptive message.
- Setup and Teardown: Lifecycle hooks that run before and after each test method, used to prepare test data and clean up resources.
- Test Naming: The string passed to the
testmacro becomes the test name. Good names describe the expected behavior, not the implementation.
Real World Context
Every pull request on a professional Rails project is expected to include tests. Reviewers check that new features have corresponding test cases and that bug fixes include a regression test proving the bug is resolved. Writing your first test is the gateway to this workflow — once you can express expected behavior as assertions, you can contribute confidently to any Rails codebase.
Deep Dive
A basic test file follows this structure:
rubyrequire 'test_helper' class ArticleTest < ActiveSupport::TestCase test 'the truth' do assert true end end
The test method is a Rails convenience that creates a method named test_the_truth under the hood. Inside the block you write one or more assertions. If every assertion passes, the test passes.
Here is a more realistic example that tests an Article model:
rubyrequire 'test_helper' class ArticleTest < ActiveSupport::TestCase def setup @article = Article.new( title: 'Testing in Rails', body: 'A comprehensive guide to writing tests.' ) end test 'valid with title and body' do assert @article.valid? end test 'invalid without title' do @article.title = nil assert_not @article.valid? end test 'title is accessible' do assert_equal 'Testing in Rails', @article.title end end
The setup method runs before every test, giving each test a fresh @article instance. This prevents tests from affecting each other through shared state.
Common assertions you will use constantly:
ruby# Boolean assertions assert expression # passes if expression is truthy assert_not expression # passes if expression is falsy # Equality assert_equal expected, actual # passes if expected == actual assert_not_equal a, b # passes if a != b # Nil checks assert_nil object # passes if object is nil assert_not_nil object # passes if object is not nil # Collections assert_empty collection # passes if collection is empty assert_includes collection, obj # passes if collection includes obj # Exceptions assert_raises(ActiveRecord::RecordInvalid) { block } # passes if block raises the error
Notice that assert_equal takes the expected value first and the actual value second. Getting this order right matters because failure messages use it: Expected: X, Actual: Y.
Common Pitfalls
- Reversing assert_equal arguments — Writing
assert_equal actual, expectedproduces confusing failure messages. Always put the expected value first:assert_equal expected, actual. - Sharing mutable state between tests — If you assign instance variables outside of
setup, one test can modify them and break another. Always initialize test data insetupso each test starts fresh.
Best Practices
- One assertion concept per test — Each test should verify one behavior. If a test has five unrelated assertions, split it into five tests with descriptive names.
- Name tests as behavior descriptions — Use names like
test 'rejects negative prices'instead oftest 'test_price_validation'. Good names serve as living documentation.
Summary
- Test files inherit from
ActiveSupport::TestCaseand use thetestmacro to define individual test cases. - Assertions like
assert,assert_equal, andassert_notverify expected behavior and produce clear failure messages. - The
setupmethod initializes fresh test data before each test, preventing cross-test contamination.
Code Examples
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
def setup
@article = Article.new(
title: 'Testing in Rails',
body: 'A comprehensive guide.'
)
end
test 'valid with title and body' do
assert @article.valid?
end
test 'invalid without title' do
@article.title = nil
assert_not @article.valid?
end
test 'title is accessible' do
assert_equal 'Testing in Rails', @article.title
end
end