Class: Matchi::Change::By

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

Overview

Note:

This matcher checks for exact changes, use ByAtLeast or ByAtMost for more flexible delta comparisons

Exact delta matcher that verifies precise numeric changes in object state.

This matcher ensures that a numeric value changes by exactly the specified amount after executing a block of code. It’s particularly useful for testing operations that should produce precise, predictable changes in numeric attributes, such as counters, quantities, or calculated values.

Examples:

Testing collection size changes

array = []
matcher = Matchi::Change::By.new(2) { array.size }
matcher.match? { array.concat([1, 2]) }     # => true
matcher.match? { array.push(3) }            # => false  # Changed by 1
matcher.match? { array.push(3, 4, 5) }      # => false  # Changed by 3

Verifying numeric calculations

counter = 100
matcher = Matchi::Change::By.new(-10) { counter }
matcher.match? { counter -= 10 }            # => true
matcher.match? { counter -= 15 }            # => false  # Changed too much

Working with custom numeric attributes

class Account
  attr_reader :balance

  def initialize(initial_balance)
    @balance = initial_balance
  end

  def deposit(amount)
    @balance += amount
  end

  def withdraw(amount)
    @balance -= amount
  end
end

 = Account.new(1000)
matcher = Matchi::Change::By.new(50) { .balance }
matcher.match? { .deposit(50) }      # => true
matcher.match? { .deposit(75) }      # => false  # Changed by 75

Handling floating point values

temperature = 20.0
matcher = Matchi::Change::By.new(1.5) { temperature }
matcher.match? { temperature += 1.5 }       # => true
matcher.match? { temperature += 1.6 }       # => false  # Not exact match

See Also:

Instance Method Summary collapse

Constructor Details

#initialize(expected, &state) ⇒ By

Initialize the matcher with an expected delta and a state block.

Examples:

With positive delta

By.new(5) { counter.value }

With negative delta

By.new(-3) { stock_level.quantity }

With floating point delta

By.new(0.5) { temperature.celsius }

Parameters:

  • expected (Numeric)

    The exact 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 no state block is provided



80
81
82
83
84
85
86
# File 'lib/matchi/change/by.rb', line 80

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?

  @expected = expected
  @state = state
end

Instance Method Details

#match? { ... } ⇒ Boolean

Verifies that the value changes by exactly the expected amount.

This method compares the value before and after executing the provided block, ensuring that the difference matches the expected delta exactly. It’s useful for cases where precision is important and approximate changes are not acceptable.

Examples:

Basic usage

counter = 0
matcher = By.new(5) { counter }
matcher.match? { counter += 5 }  # => true

Failed match

items = []
matcher = By.new(2) { items.size }
matcher.match? { items.push(1) }  # => false  # Changed by 1

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 exactly the expected amount

Raises:

  • (ArgumentError)

    if no block is provided



112
113
114
115
116
117
118
119
120
# File 'lib/matchi/change/by.rb', line 112

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:

By.new(5).to_s    # => "change by 5"
By.new(-3).to_s   # => "change by -3"
By.new(2.5).to_s  # => "change by 2.5"

Returns:

  • (String)

    A string describing what this matcher verifies



132
133
134
# File 'lib/matchi/change/by.rb', line 132

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