Created
January 28, 2012 12:14
-
-
Save yuroyoro/1694111 to your computer and use it in GitHub Desktop.
have_same_attributes : RSpec用Custom Machter。ActiveRecord/ActiverResrouce/Hashなどをゆるふわくmatchさせる
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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