Claude
Skills
Sign in
Back

ruby-test-frameworks

Included with Lifetime
$97 forever

Use when writing or running tests in a Ruby project that uses minitest or test-unit. These frameworks have deceptively similar APIs with critical naming differences that cause NoMethodError at runtime. Consult this before writing assertions, running specific tests, or using setup/teardown lifecycle methods.

Writing & Docs

What this skill does


# Minitest vs Test-Unit Divergences

Only covers where minitest and test-unit diverge. If something isn't listed here, it works the same in both.

## Assertion Naming Mismatches

Using the wrong name causes `NoMethodError`.

| Concept | minitest | test-unit |
|---------|----------|-----------|
| Exception | `assert_raises` (plural) | `assert_raise` (singular) |
| Throw | `assert_throws` (plural) | `assert_throw` (singular) |
| Collection inclusion | `assert_includes` (plural) | `assert_include` (singular) |
| Path exists | `assert_path_exists` (with s) | `assert_path_exist` (no s) |
| Negation pattern | `refute_*` (e.g. `refute_nil`) | `assert_not_*` (e.g. `assert_not_nil`) |

test-unit aliases minitest names (`assert_raises`, `refute_*`, etc.), so minitest syntax works in test-unit but **not** vice versa.

## Assertions That Only Exist in One Framework

### test-unit only

| Method | Purpose |
|--------|---------|
| `assert_nothing_raised` | Passes if block doesn't raise (also added by Rails on top of minitest) |
| `assert_true` / `assert_false` | Checks for exactly `true` or `false` (not just truthy/falsy) |
| `assert_boolean` | Checks value is `true` or `false` |
| `assert_compare` | For `<`, `<=`, `>`, `>=` comparisons |
| `assert_raise_with_message` | Checks both exception class and message |
| `assert_raise_message` | Checks exception message only (any class) |
| `assert_raise_kind_of` | Checks exception via `kind_of?` instead of exact class |
| `assert_const_defined` / `assert_not_const_defined` | Check constant existence |
| `assert_alias_method` | Verify method aliasing |
| `assert_nothing_thrown` | Passes if block doesn't call `throw` |
| `assert_block` | Passes if block returns truthy |

### minitest only

| Method | Purpose |
|--------|---------|
| `assert_pattern` / `refute_pattern` | Ruby 3+ pattern matching (`in` expressions) |
| `assert_silent` | Passes if block produces no stdout/stderr |
| `assert_output` | Check stdout/stderr content |
| `capture_io` / `capture_subprocess_io` | Capture output for inspection |
| `skip_until(y, m, d, msg)` | Skip until a date, then fail as reminder |
| `fail_after(y, m, d, msg)` | Pass until a date, then fail as reminder |

### minitest 6 breaking change

`assert_equal(nil, value)` is a hard failure in minitest 6. Use `assert_nil(value)` instead. test-unit has no such restriction.

## Test Selection (CLI Flags)

| Operation | minitest | test-unit |
|-----------|----------|-----------|
| By name (exact) | `-n test_method_name` | `--name test_method_name` |
| By name (regex) | `-n /pattern/` | `--name "/pattern/"` |
| By name (rake) | `N=/pattern/ rake test` | N/A |
| By line number | `m test/file.rb:42` or `minitest test/file.rb:42` (v6) | `--location path.rb:LINE` |
| By line (Rails) | `bin/rails test test/file.rb:42` | N/A |
| Exclude by name | `-e /pattern/` | `--ignore-name /pattern/` |
| Exclude by class | N/A | `--ignore-testcase /pattern/` |
| Filter by class | N/A (use `-n /ClassName#/`) | `--testcase ClassName` |
| Filter by attribute | N/A | `--attribute "speed == :slow"` |
| Seed | `-s SEED` or `--seed SEED` | N/A (use `--order random`) |
| Stop on failure | N/A | `--stop-on-failure` |
| Execution order | Random by default | `--order alphabetic / random / defined` |
| Color | `-p` (pride) | `--color-scheme SCHEME` |

minitest 6 renamed `-n` to `-i` (`--include`). `-n` still works as an alias but is planned for removal.

### Rails-specific flags (minitest underneath)

`bin/rails test` adds: `-b` (full backtrace), `-f` (fail-fast), `-d` (defer output), `--profile [N]` (slowest tests).

## Lifecycle Differences

### minitest

```
before_setup    (for frameworks like Rails)
  setup         (for test authors)
    after_setup
      [TEST]
    before_teardown
  teardown
after_teardown
```

No class-level hooks. No `cleanup`. For `before(:all)` / `after(:all)`, use the `minitest-hooks` gem.

### test-unit

```
startup           (class method, once before all tests)
  setup           (instance, before each test)
    [TEST]
  cleanup         (instance, only if test didn't raise)
  teardown        (instance, always runs, even if cleanup raised)
shutdown          (class method, once after all tests)
```

`startup`/`shutdown` have no minitest equivalent. `cleanup` runs after a passing test but before it's marked as passed — use it for verification like mock expectations. `teardown` always runs regardless of outcome — use it for resource release.

test-unit also supports registering multiple setup/teardown methods by symbol:

```ruby
setup :prepare_database, :prepare_cache
```

## Skip / Pending / Omit

| Concept | minitest | test-unit |
|---------|----------|-----------|
| Skip a test | `skip("reason")` | `omit("reason")` or `pend("reason")` |
| Conditional skip | `skip("reason") if condition` | `omit("reason") if condition` or `omit_if(condition, "reason")` / `omit_unless(condition, "reason")` |
| Output character | `S` | `O` (omit) or `P` (pend) |

**Critical difference**: test-unit's `pend` with a block **fails if the block passes**. This catches code that was fixed but still marked pending. minitest's `skip` is always unconditional.

```ruby
# test-unit: this FAILS if the bug gets fixed
pend("Bug #123") do
  assert_equal(42, buggy_method)  # if this passes, test fails
end

# minitest: no equivalent behavior
skip("Bug #123")  # always skips, no block form
```

## Output Format

| Aspect | minitest | test-unit |
|--------|----------|-----------|
| Progress | `.FES` | `.FEOPN` (adds Omit, Pending, Notification) |
| Summary | `N runs, N assertions, N failures, N errors, N skips` | `N tests, N assertions, N failures, N errors, N pendings, N omissions, N notifications` |
| Throughput | `runs/s, assertions/s` | `tests/s, assertions/s` |

## test-unit Exclusive Features

These have no minitest equivalent:

- **Data-driven tests**: `data("label", value)` method generates parameterized test runs
- **Priority system**: `priority :must` / `:important` / `:high` / `:normal` / `:low` / `:never` with probabilistic execution
- **Attributes**: Generic metadata on test methods, filterable via `--attribute`
- **`sub_test_case`**: Named nested test groups (minitest uses `describe` blocks in Spec mode)
- **`notify`**: Non-fatal informational messages shown as `N` in output
- **Test runners**: Console, Emacs, XML (minitest only has console with plugin system)
- **Config file**: `~/.test-unit.yml` for persistent settings

## Rails Context

Rails uses minitest, not test-unit. But Rails adds aliases and methods that blur the line:

**Aliases Rails defines on top of minitest:**

| Rails alias | Delegates to | Effect |
|-------------|-------------|--------|
| `assert_not_*` (12 methods) | `refute_*` | `assert_not_nil`, `assert_not_equal`, etc. all work in Rails |
| `assert_not` | `refute` | Base negation |
| `assert_raise` (singular) | `assert_raises` | Both singular and plural work in Rails |
| `assert_no_match` | `refute_match` | |

**Methods Rails adds (not in either framework):**

| Method | Purpose |
|--------|---------|
| `assert_nothing_raised` | Passes if block doesn't raise |
| `assert_difference` / `assert_no_difference` | Check numeric change after block |
| `assert_changes` / `assert_no_changes` | Check value change after block |

This means in Rails: `assert_not_nil`, `assert_raise`, and `assert_nothing_raised` all work — but they will **not** work if you move the test to plain minitest outside Rails.

Related in Writing & Docs