Apply any_instance to subclasses?

378 views
Skip to first unread message

Bill Kocik

unread,
May 6, 2009, 5:01:53 PM5/6/09
to mocha-developer

I couldn't find a mocha-users group, so I hope it's alright to post
user questions here. My apologies if I've guessed wrong.

Question: Is there some way to stub something not only on
any_instance, but also any_instance of any subclass?

Elaboration: My application authenticates against a 3rd party's OAuth
service. In order to test scenarios where a user is logged in, I need
to stub out authentication checks. This relies on a method defined in
ApplicationController called get_user. That's the thing I need to
stub, and I need to do it at ApplicationController because I need my
"Given I am signed in" Cucumber step to be generically reusable for
tests on all of my controllers. I tried it this way:

ApplicationController.any_instance.stubs(:get_user).returns
(Factory.create(:user))

Unfortunately when I ran my test the get_user method was called on an
instance of SplashController (which isn't a surprise), so the stub
didn't apply (which sort of was, at least to me). This is the output
of Cucumber using WebRat to drive the controllers:

Scenario: Hitting the front page while signed in # features/
register_beta.feature:13
Given I am signed in # features/
step_definitions/user_steps.rb:5
When I go to the homepage # features/
step_definitions/webrat_steps.rb:10
unexpected invocation: #<AnyInstance:SplashController>.get_user
()
satisfied expectations:
- allowed any number of times, not yet invoked:
#<AnyInstance:ApplicationController>.get_user(any_parameters)
(Mocha::ExpectationError)

Actually, now that I look at that, I'm not really sure how Mocha
became aware of the call to SplashController#get_user, since I'm not
mocking or stubbing anything there (at least not on purpose). Stubbing
the method on ApplicationController must be having some effect, just
not the one I was aiming for.

Thanks for any advice...

rgravina

unread,
Jun 1, 2009, 10:21:05 PM6/1/09
to mocha-developer


On May 7, 6:01 am, Bill Kocik <bko...@gmail.com> wrote:

> Question: Is there some way to stub something not only on
> any_instance, but also any_instance of any subclass?

I'd also like to do this, does anyone have any suggestions?

Interestingly, I'm also trying to stub out 3rd party OpenID
authentication in my Cucumber tests.

Robert

Bill Kocik

unread,
Jun 2, 2009, 12:29:15 PM6/2/09
to mocha-d...@googlegroups.com
On Tue, Jun 2, 2009 at 8:44 AM, James Mead <james...@gmail.com> wrote:

> Is SplashController#get_user defined as a method in its own right? And do
> any of the implementations of #get_user take any parameters?

The method is defined only on ApplicationController (the parent of
SplashController and others), and takes no parameters.

> As a sanity check, you could move the ApplicationController#get_user method
> to a class method on a new class e.g. OAuthService.get_user and mock that
> instead.

This method is actually a filter that runs in front of all actions in
my controllers (which is why it's defined way up on the parent
controller). A little bit more detail: The method looked like this:

def get_user
@user ||= unless session[:id].blank?
begin
User.find(session[:id])
rescue ActiveRecord::RecordNotFound
nil
end
end
end

The idea is to not bother hitting the database if there's no
information in the session. The problem was I wanted to stub this
method to return a (mocked) user regardless of session data. In the
end, I changed the method to this:

def get_user
begin
@user ||= User.find(session[:id])
rescue ActiveRecord::RecordNotFound
nil
end
end

Then I stub User.find and I'm on my way. Thankfully ActiveRecord
(which User extends) doesn't bother going to the DB when you pass nil
to find() so I'm still saved the DB hit if the user isn't logged in,
with more readable code. And I can test it.

> If you are still having trouble, could you try and reproduce the unexpected
> behaviour in a simple (non-Rails) test, post it here, and we can try and
> help.

I'm not, because I negated my need to do it. But for others who might
be interested, the code at the following URL reproduces the issue in a
dirt-simple non-Rails case:

http://pastie.org/497959

--
Bill Kocik

http://bkocik.net

James Mead

unread,
Jun 2, 2009, 4:35:48 PM6/2/09
to mocha-d...@googlegroups.com
2009/6/2 Bill Kocik <bko...@gmail.com>
Hi Bill,

Thanks for the pastie example and for being so thorough.

This all started feeling a little familiar and so I had a look through the old mailing list and found the sequence of posts below [1] which talks about a similar problem. Unfortunately it doesn't come up with the answer.

There's also a possibly more relevant thread on the current mailing list [2]. It seems like Eliot got close to a solution, but it didn't work for anonymous classes. They key seems to be that the method introduced by Mocha needs to be aware (at runtime) of the class on which the expectation was set. So if anyone can see a way of modifying the critical line [3] of Mocha::AnyInstanceMethod#define_new_method to make this work, please let me know. My meta-programming fu isn't up to it at the moment. We'd also need a new acceptance test along the lines of the existing one in StubAnyInstanceMethodTest [4].
[1] Thread :-
http://rubyforge.org/pipermail/mocha-developer/2007-June/000360.html
http://rubyforge.org/pipermail/mocha-developer/2007-June/000361.html
http://rubyforge.org/pipermail/mocha-developer/2007-July/000378.html
http://rubyforge.org/pipermail/mocha-developer/2008-January/000520.html
http://rubyforge.org/pipermail/mocha-developer/2008-January/000521.html
http://rubyforge.org/pipermail/mocha-developer/2008-January/000522.html
[2] http://rubyurl.com/zFZM
[3] http://rubyurl.com/hSKQ
[4] http://rubyurl.com/6jUx


ad...@fotocera.com

unread,
Jul 14, 2009, 11:48:33 AM7/14/09
to mocha-developer
Ran into a similar problem and though i'd share my solution.

I wanted to stub out the #ensure_proper_protocol call of the
ssl_requirement plugin so that I didn't have to worry about it in my
specs. My first attempt -- ApplicationController.stubs
(:ensure_proper_protocol).returns(true) -- failed for the very same
reason you describe in your first post, #ensure_proper_protocol is
called on a subclass of ApplicationController. I solved this by using
the following code in the spec_helper.rb file

config.before(:each) do
@controller.stubs(:ensure_proper_protocol).returns(true)
end

Hope this helps

pjammer

unread,
Aug 7, 2009, 4:25:51 PM8/7/09
to mocha-developer
So why does this work? I'm dumbfounded that this last post is the
only thing on the internet that made my tests pass.

I too had a before filter that was looking to verify an active shop.
to get around it, i wanted to stub the :active_shop? call with true,
but it wasn't working for me.

I wanted to say thank you because adding this code to my tests worked
like a charm! using Test::Unit by the way.

@controller.stubs(:active_shop).returns(true)

i'll be blogging about this for sure. I just need to find out what
the difference is between @controller and
ApplicationController.any_instance.stubs(:active_shop?).returns(true)
is.
thanks again,
Pjammer

ad...@fotocera.com

unread,
Aug 15, 2009, 9:15:06 AM8/15/09
to mocha-developer
I believe the reason why @controller works is because that variable
refers to the currently instantiated controller, so for example if the
controller you are running your tests on is OrderController <
Application Controller then @controller will refer to an instance of
OrderController. In theory you could write
OrderController.any_instance.stub(....) and that will work as well,
however then you wouls have to duplicate that code for every
controller where you needed to stub out the #active_shop? (or in my
case #ensure_proper_protocol) method.
Reply all
Reply to author
Forward
0 new messages