Usage

Table of Contents

Basic Usage

Include Minicrest::Assertions in your test class:

require 'minicrest'

class MyTest < Minitest::Test
  include Minicrest::Assertions

  def test_basic_equality
    assert_that(42).equals(42)
  end
end

Core Matchers

Value Equality

# Simple values
assert_that("hello").equals("hello")

# Arrays with deep comparison
assert_that([1, [2, 3]]).equals([1, [2, 3]])

# Hashes with deep comparison
assert_that({ user: { name: "Alice" } }).equals({ user: { name: "Alice" } })

Reference Equality

Test that two variables point to the same object:

obj = Object.new
assert_that(obj).is(obj)

Negation

assert_that(42).never(equals(0))
assert_that(nil).never(equals(false))

# Aliases for more natural flow
assert_that(42).is_not(0)
assert_that("hello").does_not(start_with("bye"))

Placeholder Matching

Use anything when you don’t care about a particular value:

assert_that(some_value).is(anything)

Use truthy or falsy to match any truthy or falsy value in Ruby:

assert_that(some_value).is(truthy)
assert_that(some_value).is(anything)

Type and Method Matchers

is_a(type) / descends_from(type)

Matches if the value is an instance of the expected type (including inheritance):

assert_that("hello").matches(is_a(String))
assert_that(42).matches(descends_from(Integer))
assert_that([]).matches(is_a(Enumerable)) # works with modules

instance_of(type)

Matches if the value is an exact instance of the expected class (no inheritance):

assert_that("hello").matches(instance_of(String))
assert_that(42).never(instance_of(Numeric)) # Integer is a Numeric, but not exactly InstanceOf Numeric

responds_to(*methods)

Matches if the value responds to all specified methods:

assert_that("hello").matches(responds_to(:upcase))
assert_that([]).matches(responds_to(:push, :pop))

Value Matchers

nil_value

Matches if the value is nil:

assert_that(nil).matches(nil_value)
assert_that(42).never(nil_value)

truthy

Matches if the value is considered true (anything except nil or false):

assert_that(true).matches(truthy)
assert_that(42).matches(truthy)
assert_that("hello").matches(truthy)

falsy

Matches if the value is considered false (nil or false):

assert_that(false).matches(falsy)
assert_that(nil).matches(falsy)

String Matchers

starts_with(prefix)

assert_that("hello world").matches(starts_with("hello"))

ends_with(suffix)

assert_that("hello world").matches(ends_with("world"))

matches_pattern(regex)

assert_that("hello123").matches(matches_pattern(/\d+/))
assert_that("test@example.com").matches(matches_pattern(/\A[\w.]+@[\w.]+\z/))

blank

Matches empty or whitespace-only strings:

assert_that("").matches(blank)
assert_that("   ").matches(blank)
assert_that("\t\n").matches(blank)

Size and Emptiness Matchers

empty

Matches empty strings, arrays, or hashes:

assert_that("").matches(empty)
assert_that([]).matches(empty)
assert_that({}).matches(empty)

has_size(expected)

Matches values with a specific size:

assert_that("hello").matches(has_size(5))
assert_that([1, 2, 3]).matches(has_size(3))
assert_that({ a: 1, b: 2 }).matches(has_size(2))

# Can use matchers for flexible size checks
assert_that([1, 2, 3]).matches(has_size(is_greater_than(2)))

Numeric Comparison Matchers

is_greater_than(expected)

assert_that(5).matches(is_greater_than(3))

is_greater_than_or_equal_to(expected)

assert_that(5).matches(is_greater_than_or_equal_to(5))
assert_that(6).matches(is_greater_than_or_equal_to(5))

is_less_than(expected)

assert_that(3).matches(is_less_than(5))

is_less_than_or_equal_to(expected)

assert_that(5).matches(is_less_than_or_equal_to(5))
assert_that(4).matches(is_less_than_or_equal_to(5))

between(min, max, exclusive: false)

Matches if the value is within the range:

assert_that(5).matches(between(1, 10))
assert_that(10).never(between(1, 10, exclusive: true))

is_close_to(expected, delta)

Floating-point equality with tolerance:

assert_that(3.14159).matches(is_close_to(3.14, 0.01))
assert_that(10.0).matches(is_close_to(10.5, 0.5))

Collection Content Matchers

includes(*items)

Matches if the value contains all specified items:

# Strings: contains substrings
assert_that("hello world").matches(includes("hello", "world"))

# Arrays: contains elements
assert_that([1, 2, 3, 4]).matches(includes(2, 4))

# Hashes: contains key-value pairs
assert_that({ a: 1, b: 2, c: 3 }).matches(includes(a: 1, c: 3))

has_key(*keys)

Matches if the hash contains all specified keys:

assert_that({ a: 1, b: 2 }).matches(has_key(:a))
assert_that({ a: 1, b: 2 }).matches(has_key(:a, :b))

has_value(*values)

Matches if the hash contains all specified values:

assert_that({ a: 1, b: 2 }).matches(has_value(1))
assert_that({ a: 1, b: 2 }).matches(has_value(1, 2))

contains(*items)

Matches if the collection contains exactly the specified items in any order:

assert_that([3, 1, 2]).matches(contains(1, 2, 3))
assert_that({ b: 2, a: 1 }).matches(contains(a: 1, b: 2))

contains_exactly(*items)

Matches if the array contains exactly the specified items in order:

assert_that([1, 2, 3]).matches(contains_exactly(1, 2, 3))

Collection Item Matchers

all_items(matcher)

Matches if all items in the collection match:

assert_that([2, 4, 6]).matches(all_items(descends_from(Integer)))
assert_that([2, 4, 6]).matches(all_items(is_greater_than(0)))

some_items(matcher)

Matches if at least one item matches:

assert_that([1, "two", 3]).matches(some_items(descends_from(String)))

no_items(matcher)

Matches if no items match:

assert_that([1, 2, 3]).matches(no_items(descends_from(String)))

all_entries(matcher) / some_entry(matcher) / no_entry(matcher)

Similar to item matchers, but specifically for hash entries (key-value pairs):

# all_entries expects a matcher that works with [key, value] arrays
assert_that({ a: 1, b: 2 }).matches(all_entries(includes(:a, :b) | includes(1, 2)))

# You can also use a Proc for more complex entry matching
assert_that({ a: 1, b: 2 }).matches(some_entry(->(entry) { entry[1] > 1 }))

Membership Matcher

is_in(collection)

Matches if the value is present in the collection:

assert_that(2).matches(is_in([1, 2, 3]))
assert_that(:a).matches(is_in({ a: 1, b: 2 }))  # checks keys
assert_that("el").matches(is_in("hello"))       # substring
assert_that(5).matches(is_in(1..10))            # ranges

Object Attribute Matcher

has_attribute(name, matcher = nil)

Matches if the object has the attribute, optionally checking its value:

User = Struct.new(:name, :age)

user = User.new("Alice", 30)

assert_that(user).matches(has_attribute(:name))
assert_that(user).matches(has_attribute(:name, equals("Alice")))
assert_that(user).matches(has_attribute(:age, is_greater_than(18)))

# Also works with hashes
assert_that({ name: "Bob" }).matches(has_attribute(:name, equals("Bob")))

Error Assertions

raises_error

Assert that a block raises an error:

# Any error
assert_that { raise "boom" }.raises_error

# Specific error class
assert_that { raise ArgumentError, "bad" }.raises_error(ArgumentError)

# Error class with message matcher
assert_that { raise ArgumentError, "bad input" }.raises_error(ArgumentError, includes("bad"))
assert_that { raise ArgumentError, "code: 123" }.raises_error(ArgumentError, matches_pattern(/\d+/))

raises_nothing

Assert that a block does not raise:

assert_that { safe_operation }.raises_nothing

Combining Matchers

Use | for OR and & for AND:

# Either/or
assert_that(status).matches(equals(:success) | equals(:pending))

# Both conditions
assert_that(value).matches(is_greater_than(0) & is_less_than(100))

# Complex combinations
assert_that(result).matches(
  (equals(1) | equals(2)) & never(equals(nil))
)

Collection Combinators

# All matchers must pass
assert_that(5).matches(all_of(is_greater_than(0), is_less_than(10)))

# No matchers should pass
assert_that(10).matches(none_of(equals(5), equals(6)))

# At least one matcher must pass
assert_that(5).matches(some_of(equals(5), equals(999)))

Custom Matchers

Create your own matchers by subclassing Minicrest::Matcher:

class BePositive < Minicrest::Matcher
  def matches?(actual)
    actual.is_a?(Numeric) && actual > 0
  end

  def description
    "be positive"
  end

  def failure_message(actual)
    "expected #{actual.inspect} to be a positive number"
  end
end

# Register the matcher
Minicrest.register_matcher(:be_positive) { BePositive.new }

# Use in tests
assert_that(5).matches(be_positive)

Parameterized Matchers

Matchers can accept arguments:

class BeDivisibleBy < Minicrest::Matcher
  def initialize(divisor)
    super()
    @divisor = divisor
  end

  def matches?(actual)
    actual % @divisor == 0
  end

  def description
    "be divisible by #{@divisor}"
  end
end

Minicrest.register_matcher(:be_divisible_by) { |n| BeDivisibleBy.new(n) }

# Use in tests
assert_that(10).matches(be_divisible_by(5))

Custom Matchers with Combinators

Registered matchers automatically work with all combinators:

# With AND/OR operators
assert_that(10).matches(be_divisible_by(5) & be_divisible_by(2))
assert_that(10).matches(be_divisible_by(3) | be_divisible_by(5))

# With never()
assert_that(7).never(be_divisible_by(2))

# With all_of, some_of, none_of
assert_that(10).matches(all_of(be_positive, be_divisible_by(5)))

Failure Messages

Minicrest provides detailed failure messages with diffs:

assert_that({ name: "Bob" }).equals({ name: "Alice" })

Output:

expected {:name=>"Bob"}
      to equal {:name=>"Alice"}

Diff:
  key :name:
    expected: "Alice"
    actual:   "Bob"

Array diffs show the index:

assert_that([1, 2, 4]).equals([1, 2, 3])

Output:

expected [1, 2, 4]
      to equal [1, 2, 3]

Diff:
  [2]:
    expected: 3
    actual:   4

String diffs show the first difference:

assert_that("hello").equals("hallo")

Output:

expected "hello"
      to equal "hallo"

Diff:
  at index 1:
    expected: "a"
    actual:   "e"

API Reference

Assertions Module - Core

Method Description
assert_that(actual, message = nil) Entry point for value assertions
assert_that { block } Entry point for block assertions
equals(expected) Value equality matcher
is(expected) Reference equality matcher (supports matchers)
anything Matches any value
never(matcher) Negates a matcher (aliases: is_not, does_not)

Assertions Module - Type & Method

Method Description
is_a(type) Type/class matcher (alias of descends_from)
descends_from(type) Type/class matcher
instance_of(type) Exact class matcher
responds_to(*methods) Method presence matcher

Assertions Module - Values

Method Description
nil_value Matches nil
truthy Matches non-nil, non-false values
falsy Matches nil or false

Assertions Module - Strings

Method Description
starts_with(prefix) String prefix matcher
ends_with(suffix) String suffix matcher
matches_pattern(regex) Regex pattern matcher
blank Blank string matcher

Assertions Module - Size & Emptiness

Method Description
empty Empty collection matcher
has_size(expected) Size/length matcher

Assertions Module - Numeric

Method Description
is_greater_than(n) Greater than comparison
is_greater_than_or_equal_to(n) Greater than or equal comparison
is_less_than(n) Less than comparison
is_less_than_or_equal_to(n) Less than or equal comparison
between(min, max, exclusive: false) Range comparison
is_close_to(n, delta) Floating-point tolerance

Assertions Module - Collections

Method Description
includes(*items) Partial containment
has_key(*keys) Hash key presence
has_value(*values) Hash value presence
contains(*items) Exact items, any order
contains_exactly(*items) Exact items, exact order
all_items(matcher) All items match
some_items(matcher) At least one matches
no_items(matcher) No items match
all_entries(matcher) All hash entries match
some_entry(matcher) At least one entry matches
no_entry(matcher) No entries match
is_in(collection) Membership check

Assertions Module - Objects

Method Description
has_attribute(name, matcher = nil) Object attribute matcher

Assertions Module - Combinators

Method Description
all_of(*matchers) All matchers must match
none_of(*matchers) No matcher should match
some_of(*matchers) At least one must match

Asserter Methods

Method Description
.equals(expected) Assert value equality
.is(expected) Assert reference equality (supports matchers)
.never(matcher) Assert negation (aliases: is_not, does_not)
.matches(matcher) Use any matcher
.raises_error(class = nil, message_matcher = nil) Assert block raises
.raises_nothing Assert block doesn’t raise

Matcher Operators

Operator Description
& AND - both matchers must succeed
| OR - at least one matcher must succeed

Module Methods

Method Description
Minicrest.register_matcher(name, &block) Register custom matcher