Class: Matchi::Change

Inherits:
Object
  • Object
show all
Defined in:
lib/matchi/change.rb,
lib/matchi/change/by.rb,
lib/matchi/change/to.rb,
lib/matchi/change/from.rb,
lib/matchi/change/from/to.rb,
lib/matchi/change/by_at_most.rb,
lib/matchi/change/by_at_least.rb

Overview

Wraps the target of a change matcher.

Defined Under Namespace

Classes: By, ByAtLeast, ByAtMost, From, To

Instance Method Summary collapse

Constructor Details

#initialize(object, method, *args, **kwargs, &block) ⇒ Change

Initialize a wrapper of the change matcher with an object and the name of one of its methods.

Examples:

Basic initialization

array = []
Change.new(array, :length)              # Track array length changes

With positional arguments

hash = { key: "value" }
Change.new(hash, :fetch, :key)          # Track specific key value

With keyword arguments

hash = { a: 1, b: 2 }
Change.new(hash, :fetch, default: 0)    # Track with default value

With block

hash = { a: 1 }
Change.new(hash, :fetch, :b) { |k| k.to_s }  # Track with block default

Parameters:

  • object (#object_id)

    The object whose state will be monitored

  • method (Symbol)

    The name of the method to track

  • args (Array)

    Additional positional arguments to pass to the method

  • kwargs (Hash)

    Additional keyword arguments to pass to the method

  • block (Proc)

    Optional block to pass to the method

Raises:

  • (ArgumentError)

    if method is not a Symbol

  • (ArgumentError)

    if object doesn’t respond to method



43
44
45
46
47
48
# File 'lib/matchi/change.rb', line 43

def initialize(object, method, *args, **kwargs, &block)
  raise ::ArgumentError, "method must be a Symbol" unless method.is_a?(::Symbol)
  raise ::ArgumentError, "object must respond to method" unless object.respond_to?(method)

  @state = -> { object.send(method, *args, **kwargs, &block) }
end

Instance Method Details

#by(delta) ⇒ #match?

Specifies the exact delta of the expected change.

Examples:

counter = 0
matcher = Change.new(counter, :to_i).by(5)
matcher.match? { counter += 5 }  # => true

Parameters:

  • delta (#object_id)

    The exact expected change amount

Returns:

  • (#match?)

    A matcher that verifies the exact change



149
150
151
# File 'lib/matchi/change.rb', line 149

def by(delta)
  By.new(delta, &@state)
end

#by_at_least(minimum_delta) ⇒ #match?

Specifies a minimum delta of the expected change.

Examples:

counter = 0
matcher = Change.new(counter, :to_i).by_at_least(5)
matcher.match? { counter += 6 }  # => true

Parameters:

  • minimum_delta (#object_id)

    The minimum expected change amount

Returns:

  • (#match?)

    A matcher that verifies the minimum change



117
118
119
# File 'lib/matchi/change.rb', line 117

def by_at_least(minimum_delta)
  ByAtLeast.new(minimum_delta, &@state)
end

#by_at_most(maximum_delta) ⇒ #match?

Specifies a maximum delta of the expected change.

Examples:

counter = 0
matcher = Change.new(counter, :to_i).by_at_most(5)
matcher.match? { counter += 3 }  # => true

Parameters:

  • maximum_delta (#object_id)

    The maximum allowed change amount

Returns:

  • (#match?)

    A matcher that verifies the maximum change



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

def by_at_most(maximum_delta)
  ByAtMost.new(maximum_delta, &@state)
end

#from(old_value) ⇒ #to

Specifies the original value in a value transition check.

Examples:

string = "foo"
Change.new(string, :to_s).from("foo").to("FOO")

Parameters:

  • old_value (#object_id)

    The expected initial value

Returns:

  • (#to)

    A wrapper for creating a from/to matcher



164
165
166
# File 'lib/matchi/change.rb', line 164

def from(old_value)
  From.new(old_value, &@state)
end

#match? { ... } ⇒ Boolean

Checks if the tracked method’s return value changes when executing the block.

This method verifies that the value changes in any way between the start and end of the block execution. It doesn’t care about the type or magnitude of the change, only that it’s different.

Examples:

Basic usage with array length

array = []
matcher = Change.new(array, :length)
matcher.match? { array << "item" }    # => true
matcher.match? { array.clear }        # => true (from 1 to 0)
matcher.match? { array.dup }          # => false (no change)

With method parameters

hash = { key: "old" }
matcher = Change.new(hash, :fetch, :key)
matcher.match? { hash[:key] = "new" }  # => true
matcher.match? { hash[:key] = "new" }  # => false (same value)

With computed values

text = "hello"
matcher = Change.new(text, :upcase)
matcher.match? { text.upcase! }        # => true
matcher.match? { text.upcase! }        # => false (already uppercase)

Yields:

  • Block during which the change should occur

Yield Returns:

  • (Object)

    Result of the block execution (not used)

Returns:

  • (Boolean)

    true if the value changed, false otherwise

Raises:

  • (ArgumentError)

    if no block is provided



83
84
85
86
87
88
89
90
91
# File 'lib/matchi/change.rb', line 83

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

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

  !value_before.eql?(value_after)
end

#to(new_value) ⇒ #match?

Specifies the final value to expect.

Examples:

string = "foo"
matcher = Change.new(string, :to_s).to("FOO")
matcher.match? { string.upcase! }  # => true

Parameters:

  • new_value (#object_id)

    The expected final value

Returns:

  • (#match?)

    A matcher that verifies the final state



180
181
182
# File 'lib/matchi/change.rb', line 180

def to(new_value)
  To.new(new_value, &@state)
end

#to_sString

Returns a human-readable description of the matcher.

Examples:

Change.new("test", :upcase).to_s # => 'eq "test"'

Returns:

  • (String)

    A string describing what this matcher verifies



101
102
103
# File 'lib/matchi/change.rb', line 101

def to_s
  "change #{@state.inspect}"
end