From d8a70a1fcdbcdb0d5c01d43e505ec612b154f0e8 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Tue, 18 Jun 2019 08:57:29 +1000 Subject: [PATCH] feat: allow Integers and Floats to be considered equivalent when using type based matching. To revert to the previous behaviour, set Pact.configuration.treat_all_number_classes_as_equivalent = false --- lib/pact/configuration.rb | 2 + lib/pact/matchers/matchers.rb | 39 +++++++++++----- ...matchers_messages_mismatched_value_spec.rb | 11 +++-- spec/lib/pact/matchers/matchers_spec.rb | 46 +++++++++++++++++-- 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/lib/pact/configuration.rb b/lib/pact/configuration.rb index 446d292..8793222 100644 --- a/lib/pact/configuration.rb +++ b/lib/pact/configuration.rb @@ -51,6 +51,7 @@ def self.=~ other attr_accessor :error_stream attr_accessor :output_stream attr_accessor :pactfile_write_order + attr_accessor :treat_all_number_classes_as_equivalent # when using type based matching def self.default_configuration c = Configuration.new @@ -61,6 +62,7 @@ def self.default_configuration c.output_stream = $stdout c.error_stream = $stderr c.pactfile_write_order = :chronological + c.treat_all_number_classes_as_equivalent = true c end diff --git a/lib/pact/matchers/matchers.rb b/lib/pact/matchers/matchers.rb index fc60c61..b0fe772 100644 --- a/lib/pact/matchers/matchers.rb +++ b/lib/pact/matchers/matchers.rb @@ -20,23 +20,30 @@ module Pact # maintain backwards compatibility module Matchers - NO_DIFF_AT_INDEX = NoDiffAtIndex.new - DEFAULT_OPTIONS = {allow_unexpected_keys: true, type: false}.freeze NO_DIFF = {}.freeze + NUMERIC_TYPES = %w[Integer Float Fixnum Bignum BigDecimal].freeze + DEFAULT_OPTIONS = { + allow_unexpected_keys: true, + type: false + }.freeze extend self def diff expected, actual, opts = {} - calculate_diff(expected, actual, DEFAULT_OPTIONS.merge(opts)) + calculate_diff(expected, actual, DEFAULT_OPTIONS.merge(configurable_options).merge(opts)) end def type_diff expected, actual, opts = {} - calculate_diff expected, actual, DEFAULT_OPTIONS.merge(opts).merge(type: true) + calculate_diff expected, actual, DEFAULT_OPTIONS.merge(configurable_options).merge(opts).merge(type: true) end private + def configurable_options + { treat_all_number_classes_as_equivalent: Pact.configuration.treat_all_number_classes_as_equivalent } + end + def calculate_diff expected, actual, opts = {} options = DEFAULT_OPTIONS.merge(opts) case expected @@ -156,22 +163,22 @@ def check_for_unexpected_keys expected, actual, options def object_diff expected, actual, options if options[:type] - type_difference expected, actual + type_difference expected, actual, options else exact_value_diff expected, actual, options end end def exact_value_diff expected, actual, options - if expected != actual - Difference.new expected, actual, value_difference_message(expected, actual, options) - else + if expected == actual NO_DIFF + else + Difference.new expected, actual, value_difference_message(expected, actual, options) end end - def type_difference expected, actual - if types_match? expected, actual + 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) @@ -186,8 +193,16 @@ def type_diff_actual_display actual actual.is_a?(KeyNotFound) ? actual : ActualType.new(actual) end - def types_match? expected, actual - expected.class == actual.class || (is_boolean(expected) && is_boolean(actual)) + # Make options optional to support existing monkey patches + 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 + + def is_number? object + # deal with Fixnum and Integer without warnings by using string class names + NUMERIC_TYPES.include?(object.class.to_s) end def is_boolean object diff --git a/spec/lib/pact/matchers/matchers_messages_mismatched_value_spec.rb b/spec/lib/pact/matchers/matchers_messages_mismatched_value_spec.rb index f53e8a9..6dbf74c 100644 --- a/spec/lib/pact/matchers/matchers_messages_mismatched_value_spec.rb +++ b/spec/lib/pact/matchers/matchers_messages_mismatched_value_spec.rb @@ -32,13 +32,14 @@ module Pact::Matchers [INT, 2, "Expected 1 but got 2 at "], [INT, nil, "Expected 1 but got nil at "], [INT, STRING, "Expected 1 but got \"foo\" at "], - [INT, FLOAT, "Expected 1 but got \"foo\" at ", {pending: true}], + [INT, FLOAT, nil], [INT, HASH, "Expected 1 but got a Hash at "], [INT, ARRAY, "Expected 1 but got an Array at "], [Pact.like(INT), 2, nil], [Pact.like(INT), nil, "Expected #{a_numeric} (like 1) but got nil at "], [Pact.like(INT), STRING, "Expected #{a_numeric} (like 1) but got a String (\"foo\") at "], - [Pact.like(INT), FLOAT, "Expected #{a_numeric} (like 1) but got a Float (1.0) at "], + [Pact.like(INT), FLOAT, "Expected #{a_numeric} (like 1) but got a Float (1.0) at ", { treat_all_number_classes_as_equivalent: false }], + [Pact.like(INT), FLOAT, nil, { treat_all_number_classes_as_equivalent: true }], [Pact.like(INT), HASH, "Expected #{a_numeric} (like 1) but got a Hash at "], [Pact.like(INT), ARRAY, "Expected #{a_numeric} (like 1) but got an Array at "], [HASH, HASH, nil], @@ -56,9 +57,9 @@ module Pact::Matchers [ARRAY, HASH, "Expected an Array but got a Hash at "] ] - COMBINATIONS.each do | expected, actual, expected_message, options | - context "when expected is #{expected.inspect} and actual is #{actual.inspect}", options || {} do - let(:difference) { diff({thing: expected}, {thing: actual}) } + COMBINATIONS.each do | expected, actual, expected_message, diff_options | + context "when expected is #{expected.inspect} and actual is #{actual.inspect}" do + let(:difference) { diff({thing: expected}, {thing: actual}, diff_options || {}) } let(:message) { difference[:thing] ? difference[:thing].message : nil } it "returns the message '#{expected_message}'" do diff --git a/spec/lib/pact/matchers/matchers_spec.rb b/spec/lib/pact/matchers/matchers_spec.rb index 667e759..ef8a04b 100644 --- a/spec/lib/pact/matchers/matchers_spec.rb +++ b/spec/lib/pact/matchers/matchers_spec.rb @@ -30,19 +30,60 @@ module Pact::Matchers end context "when the headers do not match" do - let(:actual) { Pact::Headers.new('Content-Length' => '1')} let(:difference) { {"Content-Type" => Difference.new('application/hippo', Pact::KeyNotFound.new)} } it "returns a diff" do expect(diff(expected, actual)).to eq difference end + end + end + + + context 'when treat_all_number_classes_as_equivalent is true' do + let(:options) { { treat_all_number_classes_as_equivalent: true } } + + describe 'matching numbers with something like' do + let(:expected) { Pact::SomethingLike.new( { a: 1.1 } ) } + let(:actual) { { a: 2 } } + it 'returns an empty diff' do + expect(diff(expected, actual, options)).to eq({}) + end end + describe 'with exact matching' do + let(:expected) { { a: 1 } } + let(:actual) { { a: 1.0 } } + + it 'returns an empty diff' do + expect(diff(expected, actual, options)).to eq({}) + end + end end - describe 'matching with something like' do + context 'when treat_all_number_classes_as_equivalent is false' do + let(:options) { { treat_all_number_classes_as_equivalent: false } } + describe 'matching numbers with something like' do + let(:expected) { Pact::SomethingLike.new( { a: 1.1 } ) } + let(:actual) { { a: 2 } } + + it 'returns a diff' do + expect(diff(expected, actual, options)).to_not eq({}) + end + end + + describe 'with exact matching' do + let(:expected) { { a: 1 } } + let(:actual) { { a: 1.0 } } + + it 'returns an empty diff because in Ruby 1.0 == 1' do + expect(diff(expected, actual)).to eq({}) + end + end + end + + describe 'matching with something like' do context 'when the actual is something like the expected' do let(:expected) { Pact::SomethingLike.new( { a: 1 } ) } let(:actual) { { a: 2 } } @@ -50,7 +91,6 @@ module Pact::Matchers it 'returns an empty diff' do expect(diff(expected, actual)).to eq({}) end - end context 'when the there is a mismatch of a parent, and a child contains a SomethingLike' do