Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to instantiate a Ruby class without calling initialize?

Some times when I write unit tests I need to instantiate a class without the initialize method being invoked. For instance when the constructor instantiates other classes that I will replace with stubs anyway. For instance:

class SomeClassThatIWillTest
  def initialize
    @client = GoogleAnalyticsClient.new
    @cache = SuperAdvancedCacheSystem.new
  end

  # ...
end

In a test I will probably replace both @client and @cache with stubs, so I'd rather the constructor was never invoked. Is there any black magic that can help me out with that?

like image 684
Hubro Avatar asked Jun 07 '13 11:06

Hubro


People also ask

Does a Ruby class need an initialize method?

The initialize method is useful when we want to initialize some class variables at the time of object creation. The initialize method is part of the object-creation process in Ruby and it allows us to set the initial values for an object.

How do you instantiate a class in Ruby?

An object instance is created from a class through the process called instantiation. In Ruby this takes place through the Class method new . This function sets up the object in memory and then delegates control to the initialize function of the class if it is present.

What does .NEW do in Ruby?

The new function in Ruby is used to create a new Enumerator object, which can be used as an Enumerable. Here, Enumerator is an object. Parameters: This function does not accept any parameters.

Can you call a class method on an instance Ruby?

In Ruby, a method provides functionality to an Object. A class method provides functionality to a class itself, while an instance method provides functionality to one instance of a class. We cannot call an instance method on the class itself, and we cannot directly call a class method on an instance.


3 Answers

Sure you can. Class#new is nothing more than a convenience method that saves you from having to allocate and initialize an object manually. Its implementation looks roughly like this:

class Class
  def new(*args, **kwargs, &blk)
    obj = allocate
    obj.send(:initialize, *args, **kwargs, &blk)
    obj
  end
end

You can just call Class#allocate manually instead, and not call initialize.

like image 72
Jörg W Mittag Avatar answered Sep 28 '22 02:09

Jörg W Mittag


You should not change behavior of tested class in order to unit test it. If your class will have more actions in constructor you will have to mimic it every time. Your test will get tedious to maintain. Replace objects (or even classes) with doubles.

Maybe you could provide already created objects as an arguments to the constructor? It would allow you to use doubles without stubbing new method on classes.

If you are using rspec, you can:

GoogleAnalyticsClient.stub(new: double)
SuperAdvancedCacheSystem.stub(new: double)

Define your doubles to match expected interface, and voila! No dirty tricks needed.

like image 31
samuil Avatar answered Sep 28 '22 02:09

samuil


What about subclassing SomeClassThatIWillTest and overwriting initialize in the subclass? No black magic involved ;)

This way you could even call the super initializer (to test its code, if its more than you showed us), and then alter @client and @cache afterwards.

Example of a MiniTest spec using this method:

describe MyTestClass do

  subject {
    Class.new(MyTestClass) {
      def initialize; end
    }
  }

  it "must do something" do
    subject.new.do_something.must_equal something
    # ...
  end

end
like image 23
tessi Avatar answered Sep 28 '22 02:09

tessi