Skip to content

Instantly share code, notes, and snippets.

@yuroyoro
Created January 28, 2012 12:14
Show Gist options
  • Save yuroyoro/1694111 to your computer and use it in GitHub Desktop.
Save yuroyoro/1694111 to your computer and use it in GitHub Desktop.
have_same_attributes : RSpec用Custom Machter。ActiveRecord/ActiverResrouce/Hashなどをゆるふわくmatchさせる
# Rspec用のCustom Matcher
#
# attributesメソッドを持つオブジェクト同士やHashを再帰的にmatchさせる
#
# ActiveRecord/ActiveModel/ActiveResrouce/Hashなどをゆるふわく
# 一致するか調べる。例えば、StringとSymbolは区別しないし、[]や{}やnilは同一視する。
#
# it { should have_same_attributes(hash) }
# it { should have_same_attributes(:foo => 1,:bar => 2) }
#
# exceptを付けると、除外するkeyを指定できる
#
# it { should have_same_attributes(hash).except(:hoge,:fuga) }
#
# onlyを付けると、指定したkeyのみで比較する
#
# it { should have_same_attributes(hash).only(:hoge,:fuga) }
#
# strictlyを付けると、ゆるふわ比較を止める
#
# it { should have_same_attributes(hash).strictly }
#
RSpec::Matchers.define :have_same_attributes do |expected|
@only = []
@ignored = [:id, :updated_at, :created_at, :versions]
@appended_ignored = []
chain :except do |*attr_names|
case attr_names
when Array then @ignored += attr_names.flatten
else @ignored << attr_names
end
@appended_ignored = attr_names.flatten
end
chain :only do |*attr_names|
case attr_names
when Array then @only += attr_names.flatten
else @only << attr_names
end
end
@strict = false
chain :sttictly do |flag|
@strict = flag.nil? ? true : flag
end
def has_attributes?(obj)
case obj
when Hash then true
else obj.nil? ? false : obj.respond_to?(:attributes)
end
end
def to_attributes(obj)
raise "expected hash or object but nil." if obj.nil?
hash = case obj
when Hash then obj
else
raise "undefined method 'attributes'" unless obj.respond_to?(:attributes)
obj.attributes
end
hash = hash.dup.symbolize_keys
hash.except!(*@ignored) if @ignored.present?
hash.slice!(*@only) if @only.present?
hash
end
def compare_value(this, that)
eq_proc = Proc.new{|a,b| a == b ? nil : "#{a.inspect} != #{b.inspect}" }
unless @strict
return nil if this.blank? && that.blank?
end
case value_type_of(this)
when :hash then compare_attributes(this, that)
when :array then compare_array(this, that)
when :string then
case value_type_of(that)
when :string then eq_proc.call(this.to_s, that.to_s)
else eq_proc.call(this, that)
end
else eq_proc.call(this, that)
end
rescue => e
e
end
def value_type_of(obj)
if obj.instance_of?(Array)
:array
elsif has_attributes?(obj)
:hash
elsif obj.instance_of?(String) || obj.instance_of?(Symbol)
:string
else
:object
end
end
def compare_array(this, that)
unless @strict
return nil if this.blank? && that.blank?
end
if value_type_of(that) == :array
if this.size != that.size
raise "is Array has different elements #{this.size} to #{that.size}"
end
this.zip(that).map {|a, b| compare_value(a, b) }.reject(&:blank?)
else
raise "excpected Array but #{that.class}"
end
end
def compare_attributes(this, that)
errors = {}
this_attrs = to_attributes(this)
that_attrs = to_attributes(that)
if @strict && this_attrs.keys =! that_attrs.keys
raise "has different keys"
end
this_attrs.each do |name, value|
that_value = that.respond_to?(name) ? that.send(name) : that_attrs[name]
res = compare_value(value, that_value)
errors[name] = res if res.present?
end
errors
rescue => e
e
end
match do |actual|
@errors = compare_attributes(actual, expected)
@errors.blank?
end
description do |args|
s = "have #{@strict ? 'strictly ' : '' }same attributes as #{expected.inspect}"
s += " except #{@appended_ignored.inspect}" if @appended_ignored.present?
s += " only #{@only.inspect}" if @only.present?
s += "."
end
failure_message_for_should do |args|
"have #{@strict ? 'strictly ' : '' }same attributes as #{expected.inspect}, but #{@errors.pretty_inspect}"
end
failure_message_for_should_not do |args|
"expected have different attributesas #{expected.inspect}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment