module Pact::Matchers

Should be called Differs Note to self: Some people are using this module directly, so if you refactor it maintain backwards compatibility

Constants

DEFAULT_OPTIONS
NO_DIFF
NO_DIFF_AT_INDEX
NUMERIC_TYPES

Public Instance Methods

diff(expected, actual, opts = {}) click to toggle source
# File lib/pact/matchers/matchers.rb, line 34
def diff expected, actual, opts = {}
  calculate_diff(expected, actual, DEFAULT_OPTIONS.merge(configurable_options).merge(opts))
end
type_diff(expected, actual, opts = {}) click to toggle source
# File lib/pact/matchers/matchers.rb, line 38
def type_diff expected, actual, opts = {}
  calculate_diff expected, actual, DEFAULT_OPTIONS.merge(configurable_options).merge(opts).merge(type: true)
end
Also aliased as: structure_diff

Private Instance Methods

actual_array_diff(expected, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 103
def actual_array_diff expected, actual, options
  difference = []
  diff_found = false
  length = [expected.length, actual.length].max
  length.times do | index|
    expected_item = expected.fetch(index, Pact::UnexpectedIndex.new)
    actual_item = actual.fetch(index, Pact::IndexNotFound.new)
    if (item_diff = calculate_diff(expected_item, actual_item, options)).any?
      diff_found = true
      difference << item_diff
    else
      difference << NO_DIFF_AT_INDEX
    end
  end
  diff_found ? difference : NO_DIFF
end
actual_hash_diff(expected, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 142
def actual_hash_diff expected, actual, options
  hash_diff = expected.each_with_object({}) do |(key, expected_value), difference|
    diff_at_key = calculate_diff_at_key(key, expected_value, actual, difference, options)
    difference[key] = diff_at_key if diff_at_key.any?
  end
  hash_diff.merge(check_for_unexpected_keys(expected, actual, options))
end
actual_regexp_diff(regexp, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 87
def actual_regexp_diff regexp, actual, options
  if regexp.match(actual)
    NO_DIFF
  else
    RegexpDifference.new regexp, actual, "Expected a String matching #{regexp.inspect} but got #{short_description(actual)} at <path>"
  end
end
actual_term_diff(term, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 71
def actual_term_diff term, actual, options
  if term.matcher.match(actual)
    NO_DIFF
  else
    RegexpDifference.new term.matcher, actual, "Expected a String matching #{term.matcher.inspect} (like #{term.generate.inspect}) but got #{actual.inspect} at <path>"
  end
end
array_diff(expected, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 95
def array_diff expected, actual, options
  if actual.is_a? Array
    actual_array_diff expected, actual, options
  else
    Difference.new Pact::Reification.from_term(expected), actual, type_difference_message(Pact::Reification.from_term(expected), actual)
  end
end
array_like_diff(array_like, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 120
def array_like_diff array_like, actual, options
  if actual.is_a? Array
    expected_size = [array_like.min, actual.size].max
    # I know changing this is going to break something, but I don't know what it is, as there's no
    # test that fails when I make this change. I know the unpack regexps was there for a reason however.
    # Guess we'll have to change it and see!
    # expected_array = expected_size.times.collect{ Pact::Term.unpack_regexps(array_like.contents) }
    expected_array = expected_size.times.collect{ array_like.contents }
    actual_array_diff expected_array, actual, options.merge(:type => true)
  else
    Difference.new array_like.generate, actual, type_difference_message(array_like.generate, actual)
  end
end
calculate_diff(expected, actual, opts = {}) click to toggle source
# File lib/pact/matchers/matchers.rb, line 48
def calculate_diff expected, actual, opts = {}
  options = DEFAULT_OPTIONS.merge(opts)
  case expected
  when Hash then hash_diff(expected, actual, options)
  when Array then array_diff(expected, actual, options)
  when Regexp then regexp_diff(expected, actual, options)
  when Pact::SomethingLike then calculate_diff(expected.contents, actual, options.merge(:type => true))
  when Pact::ArrayLike then array_like_diff(expected, actual, options)
  when Pact::Term then term_diff(expected, actual, options)
  else object_diff(expected, actual, options)
  end
end
calculate_diff_at_key(key, expected_value, actual, difference, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 150
def calculate_diff_at_key key, expected_value, actual, difference, options
  actual_value = actual.fetch(key, Pact::KeyNotFound.new)
  diff_at_key = calculate_diff(expected_value, actual_value, options)
  if actual_value.is_a?(Pact::KeyNotFound)
    diff_at_key.message = key_not_found_message(key, actual)
  end
  diff_at_key
end
check_for_unexpected_keys(expected, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 159
def check_for_unexpected_keys expected, actual, options
  if options[:allow_unexpected_keys]
    NO_DIFF
  else
    (actual.keys - expected.keys).each_with_object({}) do | key, running_diff |
      running_diff[key] = Difference.new(UnexpectedKey.new, actual[key], "Did not expect the key \"#{key}\" to exist at <parent_path>")
    end
  end
end
class_description(object) click to toggle source
# File lib/pact/matchers/matchers.rb, line 275
def class_description object
  return "nil" if object.nil?
  clazz = object.class
  case clazz.name[0]
  when /[AEIOU]/ then "an #{clazz}"
  else
    "a #{clazz}"
  end
end
class_name_with_value_in_brackets(object) click to toggle source
# File lib/pact/matchers/matchers.rb, line 252
def class_name_with_value_in_brackets object
  object_desc = has_children?(object) && object.inspect.length < 100 ? "" : " (#{object.inspect})"
  object_desc = if object.nil?
    "nil"
  else
    "#{class_description(object)}#{object_desc}"
  end
end
configurable_options() click to toggle source
# File lib/pact/matchers/matchers.rb, line 44
def configurable_options
  { treat_all_number_classes_as_equivalent: Pact.configuration.treat_all_number_classes_as_equivalent }
end
exact_value_diff(expected, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 177
def exact_value_diff expected, actual, options
  if expected == actual
    NO_DIFF
  else
    Difference.new expected, actual, value_difference_message(expected, actual, options)
  end
end
has_children?(object) click to toggle source
# File lib/pact/matchers/matchers.rb, line 217
def has_children? object
  object.is_a?(Hash) || object.is_a?(Array)
end
hash_diff(expected, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 134
def hash_diff expected, actual, options
  if actual.is_a? Hash
    actual_hash_diff expected, actual, options
  else
    Difference.new Pact::Reification.from_term(expected), actual, type_difference_message(Pact::Reification.from_term(expected), actual)
  end
end
is_boolean(object) click to toggle source
# File lib/pact/matchers/matchers.rb, line 213
def is_boolean object
  object == true || object == false
end
is_number?(object) click to toggle source
# File lib/pact/matchers/matchers.rb, line 208
def is_number? object
  # deal with Fixnum and Integer without warnings by using string class names
  NUMERIC_TYPES.include?(object.class.to_s)
end
key_not_found_message(key, actual) click to toggle source
# File lib/pact/matchers/matchers.rb, line 261
def key_not_found_message key, actual
  hint = actual.any? ? "(keys present are: #{actual.keys.join(", ")})" : "in empty Hash"
  "Could not find key \"#{key}\" #{hint} at <parent_path>"
end
object_diff(expected, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 169
def object_diff expected, actual, options
  if options[:type]
    type_difference expected, actual, options
  else
    exact_value_diff expected, actual, options
  end
end
regexp_diff(regexp, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 79
def regexp_diff regexp, actual, options
  if actual.is_a?(String)
    actual_regexp_diff regexp, actual, options
  else
    RegexpDifference.new regexp, actual, "Expected a String matching #{regexp.inspect} but got #{class_name_with_value_in_brackets(actual)} at <path>"
  end
end
short_description(object) click to toggle source
# File lib/pact/matchers/matchers.rb, line 266
def short_description object
  return "nil" if object.nil?
  case object
  when Hash then "a Hash"
  when Array then "an Array"
  else object.inspect
  end
end
structure_diff(expected, actual, opts = {})
Alias for: type_diff
term_diff(term, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 63
def term_diff term, actual, options
  if actual.is_a?(String)
    actual_term_diff term, actual, options
  else
    RegexpDifference.new term.matcher, actual, "Expected a String matching #{term.matcher.inspect} (like #{term.generate.inspect}) but got #{class_name_with_value_in_brackets(actual)} at <path>"
  end
end
type_diff_actual_display(actual) click to toggle source
# File lib/pact/matchers/matchers.rb, line 197
def type_diff_actual_display actual
  actual.is_a?(KeyNotFound) ?  actual : ActualType.new(actual)
end
type_diff_expected_display(expected) click to toggle source
# File lib/pact/matchers/matchers.rb, line 193
def type_diff_expected_display expected
  ExpectedType.new(expected)
end
type_difference(expected, actual, options) click to toggle source
# File lib/pact/matchers/matchers.rb, line 185
def type_difference expected, actual, options
  if types_match? expected, actual, options
    NO_DIFF
  else
    TypeDifference.new type_diff_expected_display(expected), type_diff_actual_display(actual), type_difference_message(expected, actual)
  end
end
type_difference_message(expected, actual) click to toggle source
# File lib/pact/matchers/matchers.rb, line 235
def type_difference_message expected, actual
  case expected
  when Pact::UnexpectedIndex
    "Actual array is too long and should not contain #{short_description(actual)} at <path>"
  else
    case actual
    when Pact::IndexNotFound
      "Actual array is too short and should have contained #{short_description(expected)} at <path>"
    else
      expected_desc = class_name_with_value_in_brackets(expected)
      expected_desc.gsub!("(", "(like ")
      actual_desc = class_name_with_value_in_brackets(actual)
      "Expected #{expected_desc} but got #{actual_desc} at <path>"
    end
  end
end
types_match?(expected, actual, options = {}) click to toggle source

Make options optional to support existing monkey patches

# File lib/pact/matchers/matchers.rb, line 202
def types_match? expected, actual, options = {}
  expected.class == actual.class ||
    (is_boolean(expected) && is_boolean(actual)) ||
    (options.fetch(:treat_all_number_classes_as_equivalent, false) && is_number?(expected) && is_number?(actual))
end
value_difference_message(expected, actual, options = {}) click to toggle source
# File lib/pact/matchers/matchers.rb, line 221
def value_difference_message expected, actual, options = {}
  case expected
  when Pact::UnexpectedIndex
    "Actual array is too long and should not contain #{short_description(actual)} at <path>"
  else
    case actual
    when Pact::IndexNotFound
      "Actual array is too short and should have contained #{short_description(expected)} at <path>"
    else
      "Expected #{short_description(expected)} but got #{short_description(actual)} at <path>"
    end
  end
end