Class: Matchi::Change::ByAtLeast

Inherits:
Object
  • Object
show all
Defined in:
lib/matchi/change/by_at_least.rb

Overview

Note:

This matcher verifies minimum changes only. For exact changes, use By, and for maximum changes, use ByAtMost.

Minimum delta matcher that verifies numeric changes meet or exceed a threshold.

This matcher ensures that a numeric value changes by at least the specified amount after executing a block of code. It’s particularly useful when testing operations where you want to ensure a minimum change occurs but larger changes are acceptable, such as performance improvements, resource allocation, or progressive counters.

Examples:

Testing collection growth

items = []
matcher = Matchi::Change::ByAtLeast.new(2) { items.size }
matcher.match? { items.push(1, 2) }          # => true   # Changed by exactly 2
matcher.match? { items.push(1, 2, 3) }       # => true   # Changed by more than 2
matcher.match? { items.push(1) }             # => false  # Changed by less than 2

Verifying performance improvements

class Benchmark
  def initialize
    @score = 100
  end

  def optimize!
    @score += rand(20..30)  # Improvement varies
  end

  def score
    @score
  end
end

benchmark = Benchmark.new
matcher = Matchi::Change::ByAtLeast.new(20) { benchmark.score }
matcher.match? { benchmark.optimize! }   # => true  # Any improvement >= 20 passes

Resource allocation

class Pool
  def initialize
    @capacity = 1000
  end

  def allocate(minimum, maximum)
    actual = rand(minimum..maximum)
    @capacity -= actual
    actual
  end

  def available
    @capacity
  end
end

pool = Pool.new
matcher = Matchi::Change::ByAtLeast.new(50) { -pool.available }
matcher.match? { pool.allocate(50, 100) }  # => true  # Allocates at least 50

Price threshold monitoring

class Stock
  attr_reader :price

  def initialize(price)
    @price = price
  end

  def fluctuate!
    @price += rand(-10.0..20.0)
  end
end

stock = Stock.new(100.0)
matcher = Matchi::Change::ByAtLeast.new(10.0) { stock.price }
matcher.match? { stock.fluctuate! }  # => true if price rises by 10.0 or more

See Also:

Instance Method Summary collapse

Constructor Details

#initialize(expected, &state) ⇒ ByAtLeast

Initialize the matcher with a minimum expected change and a state block.

Examples:

With integer minimum

ByAtLeast.new(5) { counter.value }

With floating point minimum

ByAtLeast.new(0.5) { temperature.celsius }

Parameters:

  • expected (Numeric)

    The minimum amount by which the value should change

  • state (Proc)

    Block that retrieves the current value

Raises:

  • (ArgumentError)

    if expected is not a Numeric

  • (ArgumentError)

    if expected is negative

  • (ArgumentError)

    if no state block is provided



101
102
103
104
105
106
107
108
# File 'lib/matchi/change/by_at_least.rb', line 101

def initialize(expected, &state)
  raise ::ArgumentError, "expected must be a Numeric" unless expected.is_a?(::Numeric)
  raise ::ArgumentError, "a block must be provided" unless block_given?
  raise ::ArgumentError, "expected must be non-negative" if expected.negative?

  @expected = expected
  @state    = state
end

Instance Method Details

#match? { ... } ⇒ Boolean

Checks if the value changes by at least the expected amount.

This method compares the value before and after executing the provided block, ensuring that the difference is greater than or equal to the expected minimum. This is useful when you want to verify that a change meets a minimum threshold.

Examples:

Basic usage

counter = 0
matcher = ByAtLeast.new(5) { counter }
matcher.match? { counter += 7 }  # => true   # Changed by more than minimum

Edge case - exact minimum

items = []
matcher = ByAtLeast.new(2) { items.size }
matcher.match? { items.push(1, 2) }  # => true  # Changed by exactly minimum

Yields:

  • Block that should cause the state change

Yield Returns:

  • (Object)

    The result of the block (not used)

Returns:

  • (Boolean)

    true if the value changed by at least the expected amount

Raises:

  • (ArgumentError)

    if no block is provided



134
135
136
137
138
139
140
141
142
# File 'lib/matchi/change/by_at_least.rb', line 134

def match?
  raise ::ArgumentError, "a block must be provided" unless block_given?

  value_before = @state.call
  yield
  value_after = @state.call

  @expected <= (value_after - value_before)
end

#to_sString

Returns a human-readable description of the matcher.

Examples:

ByAtLeast.new(5).to_s    # => "change by at least 5"
ByAtLeast.new(2.5).to_s  # => "change by at least 2.5"

Returns:

  • (String)

    A string describing what this matcher verifies



153
154
155
# File 'lib/matchi/change/by_at_least.rb', line 153

def to_s
  "change by at least #{@expected.inspect}"
end