assert_includes

P

Patrick May

Hello,

Narf [1] has a useful assert for hashes and arrays. Given a structure:

ahash = { "key1" => "value1",
"key2" => "value2" }

you can run:

assert_includes( ahash, "key1" => "value1" )

and succeed. We found this immediately useful when writing asserts on
form values, but it may a generally useful assert. Perhaps more
useful, it also supports object methods as well, as in:

class Foo
def method1
"value1"
end
def method2
"value2"
end
def method3
"value3"
end
end

assert_includes( Foo.new, "method1" => "value1", "method3" =>
"value3" )

I've found it to be a nice way to quickly check the part of an object's
state.

Attached is the implementation with tests. This code was developed by
Tom Clarke as a part of Narf, it is available under Ruby's license (as
is Narf).

Cheers,

Patrick

1. http://www.narf-lib.org

-----------------------------------------

class TestHashCompare < Test::Unit::TestCase
def test_includes_same
assert_includes( {"a" => "aa"} , {"a" => "aa"} )
end

def test_includes_missing
e = assert_raises( Test::Unit::AssertionFailedError ) {
assert_includes( { "a" => "aa", "b" => "bb" } , {"a" => "aa"} )
}
assert e.to_s.index( "Missing from actual: b => \"bb\"" )
end

def test_includes_different
e = assert_raises( Test::Unit::AssertionFailedError ) {
assert_includes( {"a" => "aa"} , { "a" => "bb" } )
}
assert e.to_s.index("Difference: required <a => \"aa\"> was <a
=> \"bb\">" )
end

def test_includes_multi_level
e = assert_raises( Test::Unit::AssertionFailedError ) {
assert_includes( {"a" => { "a" => "aa", "b" => "bb" }} , { "a"
=> { "a" => "aa", "b" => "cc"}} )
}
assert e.to_s.index("Difference: required <a.b => \"bb\"> was
<a.b => \"cc\">")
end

def test_includes_array_with_difference
e = assert_raises( Test::Unit::AssertionFailedError ) {
assert_includes( {"a" => [{ "a" => "aa" }, {"b" => "bb" }]},{ "a"
=> [ {"a" => "aa"}, {"b" => "cc"}]})
}
assert e.to_s.index("Difference: required <a[1].b => \"bb\">
was <a[1].b => \"cc\">")
end

def test_includes_array_with_items_missing
e = assert_raises( Test::Unit::AssertionFailedError ) {
assert_includes( { "a" => [ {"a" => "aa"}, {"b" => "cc"}]} , {"a"
=> [{ "a" => "aa" }]} )
}
assert e.to_s.index("Missing from actual: a[1] =>
{\"b\"=>\"cc\"}")
end

def test_includes_hash_when_should_not
e = assert_raises( Test::Unit::AssertionFailedError ) {
assert_includes( {"a" => "b"} , { "a" => { "a" => "aa", "b" =>
"cc"}} )
}
assert e.to_s.index("Difference: required <a => \"b\"> was <a
=> {\"a\"=>\"aa\", \"b\"=>\"cc\"}>")
end

def test_does_not_include_hash_when_it_should
e = assert_raises( Test::Unit::AssertionFailedError ) {
assert_includes( { "a" => { "a" => "aa" , "b" => "cc"}}, {"a" =>
"b"} )
}
assert e.to_s.index("Difference: required <a => {\"a\"=>\"aa\",
\"b\"=>\"cc\"}> was <a => \"b\">")
end

def test_includes_array_when_should_not
e = assert_raises( Test::Unit::AssertionFailedError ) {
assert_includes( {"a" => "a"}, { "a" => [ {"a" => "aa"}, {"b" =>
"cc"}]} )
}
assert e.to_s.index("Difference: required <a => \"a\"> was <a
=> [{\"a\"=>\"aa\"}, {\"b\"=>\"cc\"}]>")
end

def test_does_not_include_array_when_it_should
e = assert_raises( Test::Unit::AssertionFailedError ) {
assert_includes( {"a" => "b"} , { "a" => { "a" => "aa" , "b" =>
"cc"}} )
}
assert e.to_s.index("Difference: required <a => \"b\"> was <a
=> {\"a\"=>\"aa\", \"b\"=>\"cc\"}>" )
end

def test_assert_includes
assert_includes( {"a" => { "a" => "aa" }}, { "a" => { "a" =>
"aa" , "b" => "aa"}} )
end

def test_assert_includes2
assert_raises (Test::Unit::AssertionFailedError) {
assert_includes( { "a" => { "a" => "aa" , "b" => "cc"}},
{"a" => { "c" => "bb" },"b" => { "c" => "bb" }})
}
end

def test_assert_includes_array
assert_includes( ["a"],[ "a","b"] )
end

def test_demonstrate_failure
assert_raises (Test::Unit::AssertionFailedError) {
assert_includes(["a","b"],[ "a"])
}
end

class Trivial
attr_accessor :name
def initialize( name )
@name = name
end
end

def test_compare_object_tree_to_hash
assert_includes({"name" => "patrick"}, Trivial.new("patrick"))
end

def test_compare_object_tree_to_hash
assert_includes({"name" => { "name" => "patrick" }},
Trivial.new(Trivial.new("patrick")))
end


# TODO:
# reports an error if either item is not a hash
# improve error reporting when comparing a hash to an array and
vice versa
# add an enhanced version of assert equals
end

class Array # :nodoc:
def has_key?(index)
index >= 0 && index < length
end

def __index(key)
self[key]
end

def compare_includes? haystack, prefix=[]
message = ""
each_with_index{ |v, k|
fullname = prefix.clone
if fullname.length > 0
fullname[fullname.length-1] = "#{fullname.last}[#{k}]"
else
fullname[0] = "<Array>[#{k}]"
end
fullname_str = fullname.join(".")
message += do_thing(haystack, k, v, fullname, fullname_str)
}
message
end

def assert_includes( needle, message="" )
#assert_includes( self, needle, message )
message = needle.compare_includes? self
unless message == ""
raise Test::Unit::AssertionFailedError.new(message)
#flunk( message )
end
end
end


class Object # :nodoc:
def has_key? key
respond_to? key.intern
end

def __index(key)
send key.intern
end

def do_thing haystack, k, v, fullname, fullname_str
message = ""
# get key
if !haystack.has_key?(k)
if v
message += "Missing from actual: #{fullname_str} =>
#{v.inspect}\n"
end
else
# inspect key
# if we are looking for a collection
if v.kind_of?(Hash) || v.kind_of?(Array)
# and the haystack has a collection
if !(haystack.__index(k).kind_of?(String) ||
haystack.__index(k).kind_of?(TrueClass) ||
haystack.__index(k).kind_of?(FalseClass) ||
haystack.__index(k).kind_of?(Fixnum) ||
haystack.__index(k).kind_of?(Bignum))
# recurse if both are collections
message += v.compare_includes?(haystack.__index(k),fullname)

# otherwise, complain that we aren't the same
else
message += "Difference: required <#{fullname_str} =>
#{v.inspect}> was <#{fullname_str} => #{haystack.__index(k).inspect}>\n"
end

# if we aren't looking for a collection, see if we aren't the
same
else
haystack_value = haystack.__index(k)
if (haystack_value.kind_of? Array and
haystack_value.length == 1)
haystack_value = haystack_value.first
end
if (haystack_value != v)
message += "Difference: required <#{fullname_str} =>
#{v.inspect}> was <#{fullname_str} => #{haystack_value.inspect}>\n"
end
end
end
message
end

end

class Hash # :nodoc:
def __index(key)
self[key]
end

def compare_includes? haystack, prefix=[]
message = ""
# check that haystack is the same type?

# iterate over your self (particular to self)
self.each do |k,v|

# make a sensible name for each element of the needle (particular
to self)
fullname = prefix.clone.push k
fullname_str = fullname.join(".")

message += do_thing(haystack, k, v, fullname, fullname_str)
end

message
end

def assert_includes( needle, message="" )
message = needle.compare_includes? self
unless message == ""
raise Test::Unit::AssertionFailedError.new(message)
#flunk( message )
end
end
end

module Test # :nodoc: all
module Unit
module Assertions
def assert_includes needle, haystack, message=""
_wrap_assertion {
message = needle.compare_includes?( haystack )
unless( message == "")
raise Test::Unit::AssertionFailedError.new(message.chomp)
#flunk(message.chomp)
end
}
end
end
end
end
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,268
Latest member
AshliMacin

Latest Threads

Top