74

Yes, I know, that testing private methods it's not a good idea (and I read this thread - http://www.ruby-forum.com/topic/197346 - and some others)

But how can I test the following code?

I use xmpp4r. In my public method #listen I start receive jabber messages like so:

def listen
  @client.add_message_callback do |m|
    do_things_with_message(m)
  end
end

private
def do_things_with_message(m)
  #
end

#add_message_callback - runs block, when message come (in different thread)

So, testing #listen method it's difficult and it more testing xmpp4r than my #do_things_with_message

How to do all right and test #do_things_with_message?:) (http://www.ruby-forum.com/topic/197346#859664)

Refactor private methods to a new object essentialy would be as I making them a public (and class with one method - it's senselessly

EDIT: This is more a theoretical question about clean code and the correct tests. In my first link people argue that the test private methods poorly. I don't want cheat with #send, but also I don't see any viable ways to refactor

Andrey
  • 744
  • 1
  • 5
  • 9
  • This is not a real question. The thread you link to already gives you several techniques to accomplish what you want and some good advise about how to shape your design around the problem. You can cheat and use `send` or turn the method public in your tests, or you can refactor. – dbenhur May 16 '13 at 23:56
  • @dbenhur I don't want cheat, but also I don't see any viable ways to refactor – Andrey May 17 '13 at 00:02
  • If your code is complex enough that you need to test the private method (i.e. it's not just a simple Model.find or whatever), then it may very well be complex enough to be worth extracting into a separate class, anyway. – Jason Dec 08 '15 at 23:58

3 Answers3

135

You can call a private method in ruby using the send method. Something like this:

@my_object = MyObject.new
@my_object.send(:do_things_with_message, some_message)

In a test that would look something like:

it "should do a thing" do
  my_object = MyObject.new
  my_object.send(:do_things_with_message, some_message)
  my_object.thing.should == true
end
Eric Andrew Lewis
  • 1,256
  • 2
  • 13
  • 22
Andrew Hubbs
  • 9,338
  • 9
  • 48
  • 71
16

Putting aside the question of whether or not you should be testing a private method, it is very possible in Ruby to temporarily make a private method public. Here is what I mean:

# Metaprogrammatical magic to temporarily expose
# a Class' privates (methods).
class Class
  def publicize_methods
    saved_private_instance_methods = self.private_instance_methods
    self.class_eval { public *saved_private_instance_methods }
    yield
    self.class_eval { private *saved_private_instance_methods }
  end
end

You would use publicize_methods like this:

ClassToTest.publicize_methods do
  ...
  do_private_things_with_message(m).should ???
  ...
end
buruzaemon
  • 3,847
  • 1
  • 23
  • 44
3

You probably heard this thought in all the resources that you mention, but the proper "theoretical" way to do it would be to test that @client receives add_message_callback, and then indirectly test your private methods with integration tests. The whole point of unit testing is that you can change implementation, and your tests will still pass

enthrops
  • 729
  • 5
  • 14
  • "it would be to test that @client receives add_message_callback" - maybe you right... but it still be testing xmpp4r gem, jabber server and my network, etc. real work and real output do `#do_things_with_message`. `#add_message_callback` just launch new thread – Andrey May 17 '13 at 00:24
  • 1
    Adding a listener is not gem code, otherwise you wouldn't have to write it. If you want to test your private method, either make it public or use `send(:do_things_with_message, args)` – enthrops May 17 '13 at 00:40