Why are successive arrays collected from ObjectSpace not equal in my spec?

164 views Asked by At

I have a Project class with two ObjectSpace-related methods:

def self.all
  ObjectSpace.each_object(self).to_a
end

def self.count
  ObjectSpace.each_object(self).count
end

This spec is failing:

it "can print all projects" do
  Project.all.should eq([@project1, @project2])
end

with the following error:

Failure/Error: Project.all.should eq([@project1, @project2])

       expected: [#<Project:0x007fd76a815508 @name="Building house", @tasks=[]>, #<Project:0x007fd76a815198 @name="Getting a loan from the Bank", @tasks=[]>]
            got: [#<Project:0x007fd7688336b8 @name="Building house", @tasks=[]>, #<Project:0x007fd7688dae40 @name="Building house", @tasks=[]>, #<Project:0x007fd768af4de8 @name="Getting a loan from the Bank", @tasks=[]>, #<Project:0x007fd768af5090 @name="Building house", @tasks=[]>, #<Project:0x007fd76a815198 @name="Getting a loan from the Bank", @tasks=[]>, #<Project:0x007fd76a815508 @name="Building house", @tasks=[]>]

As you can see, this is giving me my objects in an array doubled but the code itself works fine. So why is my test failing?

3

There are 3 answers

9
Dave Newton On

Because previously-existing Projects still exist as objects.

This means they'll still be found in ObjectSpace, and you'll have more objects than you expect.

0
Todd A. Jacobs On

ObjectSpace May Contains Traces of Nuts

Well, not really. But ObjectSpace often contains objects that belong to other scopes, objects which have been marked for garbage collection but not yet removed, or (in the case of RSpec tests in particular) copies of objects from multiple invocations of a before block.

Ruby 2.0 may be different, but earlier MRI interpreters don't make guarantees about garbage collection, so you can't really count on the contents of ObjectSpace to be valid for an equality test even if you manually run GC.start.

Refactor Your Code

Instead of looking for equality in your spec, you might consider:

  1. Looking for inclusion with Array#include?
  2. Looking for specific object IDs within ObjectSpace.
  3. Refactoring the class under test, as well as the test itself. Specifically, use an aggregation pattern of some sort rather than relying on ObjectSpace to hold references to the Project objects you care about.

You can mix-and-match here, but fixing your test is really only part of the problem. The underlying application logic seems to be in need of refactoring.

The failing test is good, in that it highlights a class that needs surgery. Don't just fix the test; listen to what the spec is trying to tell you about the class under test.

5
Jasdeep Singh On

project_spec.rb

describe Project do

 let(:p1) { Project.new }
 let(:p2) { Project.new }

 describe ".all" do

  it "should keep track of all pr" do
   Project.all.should == [p1, p2]  
  end

 end

 describe ".count" do

  it "should count all the projects" do
   Project.count.should == 2
  end

 end

end

project.rb

class Project

  @@all_projects = []

  def initialize(options=nil)
    @@all_projects << self
  end

  def self.all
    @@all_projects
  end

  def self.count
    @@all_projects.count
  end

end


Finished in 0.00079 seconds
2 examples, 0 failures

I'll leave it upto you to work out the other details that might be specific and intricate to your project. Hope this works out for you.