Arman H
Arman H

Reputation: 5618

How to unit-test sameness/equality of class instances in Ruby?

I'm trying to unit test sameness of two instances of one class in Ruby:

def test_example
    a = Object.new
    b = Object.new

    assert_equal a, b
end

I understand that this fails because the instances are distinct variables each with its own memory pointer. What I'm after is to get this test to pass if the instances are identical in all respects but their reference pointers.

Here's a more involved (if contrived) example:

# Let's stir up something...
class FooBar
    attr_accessor :seed, :foo, :bar

    def foo_the_bar()
        @bar = @seed + @foo * 3
    end
end

f = FooBar.new
f.seed = "Mountains "
f.foo  = "are just mountains "
f.bar = "obliterate me!"
f.foo_the_bar
p f.bar # "Mountains are just mountains are just mountains are just mountains "


# So far so good, now let's test if one FooBar is same as another...

require 'test/unit'
class FooBarTest < Test::Unit::TestCase

    # Test fails: <#<FooBar:0x9a40d18>> expected but was <#<FooBar:0x9a40d04>>.
    # But what we really want is a way to make this pass
    # since the objects are exactly the same in every respect,
    # besides their pointers.
    def test_foobar_1_init
        f1 = FooBar.new
        f2 = FooBar.new

        assert_equal f1, f2
    end

    # Again, test fails, but we want it to pass,
    # since the instance variables are the same.
    def test_foobar_2_instance_var
        f1 = FooBar.new
        f2 = FooBar.new

        f1.seed = "Santa says "
        f1.foo = "ho "
        f1.bar = "something"
        f1.foo_the_bar

        f2.seed = f1.seed
        f2.foo = f1.foo
        f2.foo_the_bar

        assert_equal f1, f2
    end

    # This test fails for the wrong reason.
    # We want this to fail because the instance variables are different.
    # This test should also fail if we add a method to one of the instances,
    # or make the instances differ from each some other way.
    def test_foobar_3_diff
        f1 = FooBar.new
        f2 = FooBar.new

        f1.seed = "Kitty goes "
        f1.foo = "meow "
        f1.foo_the_bar

        f2.seed = "Doggies goes "
        f2.foo = "woof "
        f2.foo_the_bar

        assert_equal f1, f2
    end
end

Upvotes: 4

Views: 747

Answers (3)

Victor Deryagin
Victor Deryagin

Reputation: 12225

Just define FooBar#== method:

class FooBar
  def ==(other)
    [bar, seed, foo] == [other.bar, other.seed, other.foo]
  end
end

now test_foobar_1_init and test_foobar_2_instance_var pass, test_foobar_3_diff fails for the right reason.

A downside is when you change object structure, == method needs to be modified accordingly.

Upvotes: 1

ShadyKiller
ShadyKiller

Reputation: 710

As per the source at apidock, assert_equals first converts objects to string using inspect method. You should define the inspect method for the class FooBar

class FooBar
  def inspect
    # create a unique string of attributes here, maybe a json string.
  end
end

Upvotes: -1

Ravi Sankar Raju
Ravi Sankar Raju

Reputation: 2950

f1.attributes.keys.collect(&:to_sym).each do |field|
 assert_equal f1.send(field), f2.send(field)
end

This will assert equality of all the fields. But the downside is the number of assertions. If you dont want that to happen, assign ids to the objects like this

f1.id = 1
f2.id = 1 
assert_equal f1, f2

But be sure not to save the objects which might lead to inconsistencies.

Upvotes: 1

Related Questions