RSpec: How to test that a class's initializer method invokes some method?

6,021 views
Skip to first unread message

Gabe Hollombe

unread,
Nov 21, 2010, 5:46:24 PM11/21/10
to rails-oceania
Hey peeps:

Let's say I've got a Person class like so:

class Person
  def initialize
    do_something
  end

  private
  
  def do_something
    #do something useful
  end
end

Now, let's say I'd like to write a spec to ensure that Person#do_something is called when I call Person#new

What's the right way to test for this?  RSpec's built in message expectations, using 'should_receive', need to be placed on an instance of an object before I trigger the method that should end up triggering the expected invocation.  But, in this case, I can't set a should_receive expectation because I don't have a handle on the new Person instance until after #initialize is done doing its work.  I could explore other message expectation frameworks, like RR, that are supposed to let me tack on the expectations post facto, instead of a priori (wow, did I really just write "post facto" and "a priori" in the same sentence?), but I'm wondering if there's a built-in RSpec-y way to do this.

Help a brother out?

-g

Craig Ambrose

unread,
Nov 21, 2010, 5:53:23 PM11/21/10
to rails-...@googlegroups.com
Hi Gabe,

You really don't want to place expectations (like should_receive) on the class under test. You don't actually want to test that do_something is called, that's a private method, and thus ties your specs to the implementation. What you're wanting to test is that the bit where the "#do something useful" comment is actually happens. That's similar, but obviously your example doesn't say what that is.

Generally, a method call as an affect. Either it changes the object under test, or it does something to other objects. If it's other objects, place expectations on them. If changes the object under test, interrogate it after the test method (initialiser in this case) and see if it has the required state.

If you're stuck on how to do that, post some code or pseudocode for "#do something useful" and I can make some suggestions.

Craig

Daniel N

unread,
Nov 21, 2010, 5:53:29 PM11/21/10
to rails-...@googlegroups.com
In this case you can allocate the object which creates it, but doesn't call the initialze method on it.

person = Person.allocate
person.should_receive(:do_something)
person.send(:initialize)

However, the mocks / stubs are horrible... Is there a behavior you can observe rather than checking a method call?

Cheers
Daniel

--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
To post to this group, send email to rails-...@googlegroups.com.
To unsubscribe from this group, send email to rails-oceani...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.

Pat Allan

unread,
Nov 21, 2010, 5:55:23 PM11/21/10
to rails-...@googlegroups.com
Heya Gabe

I'd recommend against testing whether a private method has been called. I treat private methods as the black box internals of the class - you should be able to refactor them without any tests breaking.

What I'd more be looking to test is what I would expect those private methods to do. Are they sending an email? Setting an attribute? What's the (public) behaviour you want to see?

Cheers

--
Pat

Julio Cesar Ody

unread,
Nov 21, 2010, 5:57:50 PM11/21/10
to rails-...@googlegroups.com
> What's the (public) behaviour you want to see?

And that's the question that, if everyone doing BDD asked themselves
before writing specs, we'd have more BDD and less test-nutty driven
development.

ps: no stab meant at anyone! :)

--
http://awesomebydesign.com

Ivan Vanderbyl

unread,
Nov 21, 2010, 6:01:50 PM11/21/10
to rails-...@googlegroups.com
Hi Gabe,

I tend not to write assertions around private methods, rather to test the public API they should manipulate, and design your private methods in a way which they can be refactored (hopefully) without breaking the public API.

If you're trying to assert this private method getting called, you might be better off actually testing that it's done it's job correctly after being called.

- Ivan

On 22/11/2010, at 9:46 AM, Gabe Hollombe wrote:

Gareth Townsend

unread,
Nov 21, 2010, 6:06:35 PM11/21/10
to rails-...@googlegroups.com

> In this case you can allocate the object which creates it, but doesn't call the initialze method on it.
>
> person = Person.allocate
> person.should_receive(:do_something)
> person.send(:initialize)

This is what I have done previously, and it works great.

As others have said though, you might want to re-think why you need to test this directly, as opposed to observing the behaviour this changes in your class.

Gabe Hollombe

unread,
Nov 21, 2010, 6:11:28 PM11/21/10
to rails-...@googlegroups.com
Let's look at a real-world example instead of my contrived Person with
private #do_something crappy example.

From https://github.com/gabehollombe/DirtyMUD/blob/master/spec/dirtymud/entity_spec.rb
it 'listens to server ticks when it is created'
#I'd like to test that Entity#observe(server) is called when I call
Entity.new(:server => server), but how?


The Entity class file:
https://github.com/gabehollombe/DirtyMUD/blob/master/lib/dirtymud/entity.rb
The Responder module that Entity mixes in to give it #observe:
https://github.com/gabehollombe/DirtyMUD/blob/master/lib/dirtymud/responder.rb
#Observer::observe
def observe(obj, do_observe=true)
if do_observe
obj.add_observer(self)
else
obj.delete_observer(self)
end
end

What I'd like to do is test that when I call Entity.new(:server =>
server), the new Entity that's created calls observe(server). Am I
still thinking about this the wrong way?

Clifford Heath

unread,
Nov 21, 2010, 6:17:22 PM11/21/10
to rails-...@googlegroups.com
Don't you actually want to know whether your new Entity can see server
ticks?
So why not create one and send a tick, verifying that it hears?
You might need to mock the server, but that sounds like a good idea
anyhow.

Clifford Heath.

On 22/11/2010, at 10:11 AM, Gabe Hollombe wrote:

> Let's look at a real-world example instead of my contrived Person with
> private #do_something crappy example.
>
> From https://github.com/gabehollombe/DirtyMUD/blob/master/spec/dirtymud/entity_spec.rb
> it 'listens to server ticks when it is created'
> #I'd like to test that Entity#observe(server) is called when I call
> Entity.new(:server => server), but how?
>
>
> The Entity class file:
> https://github.com/gabehollombe/DirtyMUD/blob/master/lib/dirtymud/entity.rb
> The Responder module that Entity mixes in to give it #observe:
> https://github.com/gabehollombe/DirtyMUD/blob/master/lib/dirtymud/responder.rb
> #Observer::observe
> def observe(obj, do_observe=true)
> if do_observe
> obj.add_observer(self)
> else
> obj.delete_observer(self)
> end
> end
>
> What I'd like to do is test that when I call Entity.new(:server =>
> server), the new Entity that's created calls observe(server). Am I
> still thinking about this the wrong way?
>
>

> On Mon, Nov 22, 2010 at 6:55 AM, Pat Allan <pat@freelancing-

Pat Allan

unread,
Nov 21, 2010, 6:21:07 PM11/21/10
to rails-...@googlegroups.com
What do you expect an entity to do when a server tick happens? That's what I'd be testing.

And if it's different for different situations, then you've got more specs to write :)

--
Pat

Craig Ambrose

unread,
Nov 21, 2010, 6:22:50 PM11/21/10
to rails-...@googlegroups.com

class TestServer
  attr_accessor :observers
  def add_observer(observer)
    @observers ||= []
    @observers << observer
  end
end

describe "when instantiated"
  it "should start observing server" do
    server = TestServer.new
    entity = Entity.new(server)
    server.observers.should == [entity]
  end
end

Note that I can't use server.should_receive because I couldn't check that it's passed the entity object, since it isn't created until the method under test is called. There might be some library to create "recording stubs" of some sort, but in my example above I just created a dummy server class in the test file. In general, it's probably not a good idea that your spec file uses a real Dirtymud::Server object. 

Also, I named my spec differently to yours. I think we can presume that the server correctly informs it's observers of it's events (like "tick"), since that would be tested in the real server's specs. So, we're really just specifying that the entity should start observing it. We would then obviously also need to test it's handling of the tick event in another spec.

Craig

Gabe Hollombe

unread,
Nov 21, 2010, 6:25:39 PM11/21/10
to rails-...@googlegroups.com
Yes, Clifford / Pat / everyone else who suggested testing behavior. I
can totally just go and add specs for testing the context of the
Server firing the tick event.

I see/agree that if I have a spec where I have a server fire off a
tick_event, and make sure that the Entity is handling that properly,
then it eliminates the need for having to assert that
Entity#initialize ever called observe(server) since I'll be testing
the ultimate outcome of the server tick and not the lower unit-level
of the Entity class's implementation. Fair enough, I'll just go about
it that way.

Thanks everyone, as usual, for your feedback. I <3 you guys.

-g

>> On Mon, Nov 22, 2010 at 6:55 AM, Pat Allan <p...@freelancing-gods.com>

Gabe Hollombe

unread,
Nov 21, 2010, 6:28:11 PM11/21/10
to rails-...@googlegroups.com
Yep, Craig, I knew I could go about doing something like this. It
just felt a little nasty having to create a mock server object that
overrides #add_observer to keep track of the observers and use
@observers to show that the newly created Entity is included among
them. Thanks for echoing at least one of my internal ideas of how I
could get around this situation.

And, yes, I know I probably don't want to be creating real Server
instances in my Entity spec. Plenty of refactoring to be done. It's
part of the reason why it's called DirtyMUD, for now. =-D

-g

Bayan Khalili

unread,
Nov 21, 2010, 7:15:19 PM11/21/10
to rails-...@googlegroups.com
Mocha has any_instance:


Regards,
Bayan

--

Craig Ambrose

unread,
Nov 21, 2010, 7:20:13 PM11/21/10
to rails-...@googlegroups.com
I'm not using mocha on my current project, and I do notice myself missing any_instance every now and then. It's *very* handy for making specs work when you can't figure out how else to do them.

However, perhaps because of that, nine times out of ten when I've actually used any_instance in a spec, I've looked at it afterwards and realised that the spec was a total cop-out that didn't truly force me to write the correct code, or drive the design in a positive way. Maybe your experiences with it are better, but in general I see it as a faintly bad smell that tends to indicate that the design was too tightly coupled to test properly.

Craig

Bayan Khalili

unread,
Nov 21, 2010, 8:12:47 PM11/21/10
to rails-...@googlegroups.com
I agree, it shouldn't be used as a cop out.

IMO it should generally be used to stub out behaviour, not set expectations, as was required in the examples in this thread - sorry, my bad. I was just given an answer to "how do you do x" instead of answering with another question: "why do you want to do x" ;)

Although I don't use it very often, I believe there are some good use cases.

Bayan


Nicholas Faiz

unread,
Nov 21, 2010, 9:22:02 PM11/21/10
to Ruby or Rails Oceania
As a side note, I try to avoid adding any complex logic into
initializers, as they can become nasty if they fail. These pieces of
code start off simply, but later code additions, etc., can make the
dependency on a method call to object creation a bit fragile. Much
better to call do_something explicitly, in most cases, imo.

Rimian Perkins

unread,
Nov 22, 2010, 12:48:50 AM11/22/10
to rails-...@googlegroups.com, sh...@astromultimedia.com
Hi All,

A friend of mine is running a co working warehouse in Brunswick East, Melbourne. It looks like a decent place to work if you're a freelancer and need some space. He needs to get the word out to keep it all running smoothly so I thought I would post it here.


Contact: Shaun Moss - sh...@astromultimedia.com

Cheers,
@Rimian




Chris Herring

unread,
Nov 22, 2010, 1:03:07 AM11/22/10
to rails-...@googlegroups.com
Posting it on desksnear.me would be a great way to get the word out.

Keith Pitt

unread,
Nov 22, 2010, 1:31:54 AM11/22/10
to rails-...@googlegroups.com
+1 to adding it on desksnear.me </shamelessplug>

Keith Pitt
Web: http://www.keithpitt.com
Twitter: @keithpitt
Skype: keithpitt
Phone: +61 432 713 987
Reply all
Reply to author
Forward
0 new messages