Minitest assertions

The class under test

A test needs something with a rule worth checking. The calculator below has one normal path and one error path:

class Calculator def add(a, b) = a + b def divide(a, b) raise ArgumentError, "division by zero" if b.zero? a / b end end

gem install minitest

2 gems installed

assert and refute

A test class inherits from Minitest::Test, and every method that starts with test_ is one test. assert_equal takes the expected value first, which is what the failure message prints:

require "minitest/autorun" require_relative "../lib/calculator" class CalculatorTest < Minitest::Test def test_add assert_equal 4, Calculator.new.add(2, 2) end def test_not_nil refute_nil Calculator.new end end

refute is assert with the condition inverted, and refute_nil is the common case with a better message. The one-assertion rule is a guideline: a test with five assertions fails on the first and hides the others, so each test names one behaviour.

assert_raises

The error path is a test too. assert_raises takes the class, runs the block and returns the exception, so the message can be checked as well:

def test_divide_by_zero error = assert_raises(ArgumentError) { Calculator.new.divide(1, 0) } assert_equal "division by zero", error.message end

Without assert_raises the exception escapes and the test is reported as an error, not a failure. The difference matters: a failure is a wrong value, an error is code that did not return.

setup and teardown

setup runs before every test and teardown after it, whatever the test did. The instance variable is fresh for each test, which is what stops one test from depending on another:

class CalculatorTest < Minitest::Test def setup @calc = Calculator.new end def teardown @calc = nil end def test_add assert_equal 4, @calc.add(2, 2) end end

Running it

ruby test/calc_test.rb

... Finished in 0.001132s, 2649.860 runs/s, 3533.147 assertions/s. 3 runs, 4 assertions, 0 failures, 0 errors, 0 skips

Three tests, four assertions: the error test makes two. Every line of the summary is read by a person or a parser, and the exit status is zero only when failures and errors are both zero.

A single test is run by name: ruby test/calc_test.rb -n test_add, and a whole suite by a glob: ruby -Itest test/*_test.rb.

What to watch for

A test that asserts the current output of a bug is a test that protects the bug. Write the expected value from the rule, not from a run.

Random order is a feature: a suite that only passes in one order has a test that changes shared state. Minitest shuffles per run, and the seed in the output is the way to repeat a failing order.

Next: Testing a web app

Article author: Arthur Isaev

Banner image