With Minitest, what makes a method in a Minitest::Test subclass run as a test, and when do setup and teardown run?
answer
- subclass Minitest::Test
- public methods matching /^test_/
- one new instance per test
- setup before, teardown after, every test
- teardown runs after a failure too
basics
~10 sMinitest runs every public method whose name starts with test_ in a Minitest::Test subclass, each on a fresh instance. setup runs before every test and teardown after it, even when the test failed.
solid answer
~40 sYou `require "minitest/autorun"`, subclass `Minitest::Test` and write public methods whose names start with `test_`; Minitest collects them with `public_instance_methods(true).grep(/^test_/)` and, by default, runs them in random order. Every test method runs on a **new instance** of the class, so the instance variables `setup` assigns are rebuilt for each test and never leak into the next one. The per-test lifecycle is `setup`, the test method, then `teardown`, and `teardown` still runs when an assertion failed or the code raised, so it is where you close files or restore global state. A failed assertion raises `Minitest::Assertion` and is reported as a Failure (`F`), any other exception as an Error (`E`), and `skip` as `S`.
code
ruby · 21 linesrequire "minitest/autorun"
class FineCalculatorTest < Minitest::Test
def setup
@calc = FineCalculator.new(daily_rate_cents: 25, cap_cents: 1_000)
@log = File.open("fines.log", "w")
end
def teardown
@log.close
File.delete("fines.log")
end
def test_no_fine_when_on_time
assert_equal 0, @calc.fine_for(0)
end
def caps_the_fine_test # never runs: no test_ prefix
assert_equal 1_000, @calc.fine_for(90)
end
endgo deeper
Recall the three rules: subclass Minitest::Test, prefix methods with test_, and use setup and teardown for per-test preparation and cleanup.
Explain that each test method gets a new instance, that teardown runs in its own wrapper after failures, and how F, E and S differ in the report.
Show you can spot state that escapes the per-instance model, such as constants, class variables, globals and ENV, and put its restoration in teardown.
Frame the per-test instance and random order as deliberate isolation defaults, and judge when a shared base class or module helps a suite versus hiding setup.
## The shape of a Minitest test file Minitest is the testing library Ruby bundles (Ruby 4.0.7 ships Minitest 6.0.0 as a bundled gem). Its classic style, `minitest/test`, is plain Ruby classes and methods: ```ruby require "minitest/autorun" require_relative "../lib/fine_calculator" class FineCalculatorTest < Minitest::Test def setup @calc = FineCalculator.new(daily_rate_cents: 25, cap_cents: 1_000) end def test_charges_the_daily_rate assert_equal 75, @calc.fine_for(3) end def test_caps_the_fine assert_equal 1_000, @calc.fine_for(90) end end ``` `require "minitest/autorun"` loads Minitest (both the test and the spec layer) and registers an `at_exit` hook, so running `ruby -Ilib test/fine_calculator_test.rb` executes every test class that was loaded. ## What counts as a test - The class must inherit from **`Minitest::Test`** (directly or through your own base class). - A test is any **public** instance method whose name matches `/^test_/`. Minitest finds them with `public_instance_methods(true)`, so `test_` methods inherited from a superclass or an included module run too, and a `private` `test_` method does not. - Helper methods without the prefix (`def build_member`) are ordinary methods you call from tests. - There are no annotations or registration calls; the name is the whole contract. By default the methods of a class run in **random order**, seeded so that a failing order can be replayed. That default exists to expose tests that secretly depend on each other. ## One instance per test, and the lifecycle around it For each test method Minitest creates a **new instance** of the class, passing the method name, and calls `run` on it. Inside `run` the order is fixed: 1. `before_setup`, `setup`, `after_setup` - the middle one is yours; the outer two are hooks for plugin authors. 2. The test method itself. 3. `before_teardown`, `teardown`, `after_teardown` - each wrapped separately, so all of them run even when the test body, or `setup`, raised. Because the instance is new every time, `@calc` assigned in `setup` is a fresh object for each test: if `test_caps_the_fine` mutated it, `test_charges_the_daily_rate` would never see the change. Anything stored in a constant, a class variable or a global, however, is shared across tests, and `setup` does not reset it. | hook | runs | typical job | |---|---|---| | `setup` | before every test | build the object under test, open a temp dir | | `teardown` | after every test, pass or fail | close files, restore `ENV`, delete temp data | | `before_setup` / `after_teardown` | around the above | reserved for libraries extending Minitest | If you mix a module with its own `setup` into several test classes, call `super` inside it so the chain still reaches every definition. ## How outcomes are reported An assertion that fails raises **`Minitest::Assertion`**, which stops that test method at that line. Note that `Minitest::Assertion` inherits from `Exception`, not `StandardError`, so a bare `rescue => e` in code under test does not swallow a failed assertion. Minitest's wrapper then records the result: - `.` - the test passed. - `F` - **Failure**: an assertion did not hold. - `E` - **Error**: some other exception escaped (a `NoMethodError`, an `ArgumentError` you did not expect). It is wrapped in `Minitest::UnexpectedError`. - `S` - **Skipped**: the test called `skip "reason"`, which raises `Minitest::Skip`. The summary line reads `N runs, N assertions, N failures, N errors, N skips`. The Failure/Error split matters when you read a red build: an `F` means the code returned the wrong answer, an `E` usually means the test or the code crashed before it could answer. ## Common first-week mistakes - Naming a method `caps_the_fine_test` or `check_cap`: it never runs, and the suite stays green without testing anything. - Putting shared state in `@@count` or a constant and expecting `setup` to reset it. - Doing cleanup at the end of the test body instead of in `teardown`, so a failing assertion skips the cleanup and pollutes later tests. - Overriding `setup` in a subclass without calling `super` when the parent class also builds fixtures.
- In Minitest, setup assigns @calc and test_a calls @calc.reset_rate!(0). Does test_b see the zero rate?No. Minitest builds a new instance of the test class for every test method, and `setup` runs again on that instance, so `test_b` gets a freshly constructed `@calc`. The change would leak only if the state lived somewhere shared, such as a constant, a class variable or a global.
- In Minitest, what is the difference between a Failure and an Error in the run summary?A Failure (`F`) is a `Minitest::Assertion` raised by an assertion that did not hold: the code answered wrongly. An Error (`E`) is any other exception escaping the test, wrapped in `Minitest::UnexpectedError`: the code or the test crashed. Both turn the run red, but they point at different fixes.
- With Minitest, why should cleanup live in teardown rather than at the end of the test method?A failing assertion raises and stops the test method at that line, so cleanup written after it never runs. `teardown` is called by the test's `run` method in its own exception wrapper after the body, whether the body passed, failed, errored or skipped, so files, `ENV` changes and temp data are always restored.
Each test is an exam candidate handed a brand-new answer sheet: setup fills in the header, the candidate writes, teardown collects the sheet whether the answers were right or wrong, and nobody inherits the previous candidate's scribbles.
saying these in an interview costs you the question
- Any method in the test class runs as a test, whatever its name.
- setup runs once per class, so tests share one @calc object.
- teardown is skipped when an assertion in the test fails.
- Tests run top to bottom in the order they are written in the file.
- A failed assertion is reported the same way as an unexpected exception.