markain
markain

Reputation: 381

rspec equality matchers failing, objects are seemingly the same

I'm writing a program that allows users to match orders in transactions that end up transferring things between them.

I have a class, TransferAsset,that takes a transaction and creates an asset for the transaction's purchaser:

class TransferAsset
  attr_accessor :transaction, :seed_asset

  def initialize(opts = {})
    @transaction = opts[:transaction]
  end  

  def create_seed_asset
    @seed_asset = UserInventoryFactory.new({transaction: transaction}).inventory
  end
end

Here's the UserInventory class:

class UserInventoryFactory
  attr_accessor :transaction, :inventory

  def initialize(opts = {})
    @transaction = opts[:transaction]
    create_user_inventory
    set_values
  end

  def set_values
    set_seed_transaction
    set_owner
    set_amount
    set_purchase_date
    set_initial_amount
  end

  def create_user_inventory
    @inventory = UserInventory.new
  end

  def set_seed_transaction
    inventory.seed_transaction_id = transaction.id
  end

  def set_owner
    inventory.user_id = transaction.buyer_id
  end

  def set_amount
    inventory.amount = transaction.amount
  end

  def set_purchase_date
    inventory.purchase_date = transaction.created_at.beginning_of_day
  end

  def set_initial_amount
    inventory.initial_amount = transaction.amount
  end

end

I have an rspec test for the TransferAsset class:

describe '#create_seed_asset' do
  it 'creates an asset for the purchaser' do
    factory = UserInventoryFactory.new({transaction: transaction})
    expect(transfer.create_seed_asset).to eql factory.inventory
  end
end

The test fails with:

1) TransferAsset#create_seed_asset creates an asset for the purchaser
 Failure/Error: expect(transfer.create_seed_asset).to eql factory.inventory

   expected: #<UserInventory id: nil, amount: 5000.0, initial_amount: 5000.0, purchase_date: "2014-06-22 05:00:00", user_id: 2, seed_transaction_id: 1, created_at: nil, updated_at: nil>
        got: #<UserInventory id: nil, amount: 5000.0, initial_amount: 5000.0, purchase_date: "2014-06-22 05:00:00", user_id: 2, seed_transaction_id: 1, created_at: nil, updated_at: nil>

   (compared using eql?)

I've tried all of the rspec equality matchers, and it always fails, which makes me think that I'm writing bad code, either in my test, or in my actual classes.

What am I doing wrong or what can I do better?

Upvotes: 0

Views: 4408

Answers (3)

pymkin
pymkin

Reputation: 4544

Since you just want to test for object equivalence, you could convert your objects to JSON and compare the results.

It avoids having to redefine equality methods. So you could change your test to look like this:

describe '#create_seed_asset' do
  it 'creates an asset for the purchaser' do
    factory = UserInventoryFactory.new({transaction: transaction})
    expect(transfer.create_seed_asset.to_json).to eql factory.inventory.to_json
  end
end

Upvotes: 1

Uri Agassi
Uri Agassi

Reputation: 37409

You are trying to compare two different instances of UserInventory class, each created in create_user_inventory method:

def create_user_inventory
  @inventory = UserInventory.new
end

Comparing the two gives you false, just like the following:

Object.new.eql? Object.new
# => false

Having two objects with the same class (and even the same attribute values) does not make them equal, and you can't expect the default compare methods to know that they are.

Objects like strings, arrays and maps have their own implementation of comparators with custom semantics, and if you want your classes to have their own semantics - you should also implement their custom comparators, and define those semantics.

See also here:

Ruby has three main equality test methods, ==, eql? and equal?. These methods normally live in the Object class and since all other Ruby classes inherit from Object, they automatically gain access to these three methods. Inside the Object class all there methods do exactly the same thing, they test if two objects are exactly the same object. That is to say, both objects must have the same object id.

Upvotes: 1

Brett Banks
Brett Banks

Reputation: 170

You need to overwrite equality methods for your class. Right now it is defaulting to comparing object ids and since they aren't the same object in memory it will always be false.

Try overriding == to compare the instance variables of the two objects

The default behavior for == for new classes, ignoring inheritance, is to compare the object ids

Upvotes: 2

Related Questions