cancancan mock load_and_authorize_resource - ruby-on-rails

I have a test where I stub a class "ClassA". And I want Cancancan's load_and_authorize_resource to load my stubbed object, instead of fetching it from the database.
class ClassAController < ApplicationController
load_and_authorize_resource :classa
end
I've tried different ways with mocha, but with no success
CanCan::ControllerResource.any_instance.stubs(:load_and_authorize_resource).returns(#stubbed_classA)
does not load the #classA object, as is supposed to
Or, using responds_like
#stubb_classA = stub(...)
...
class MOCKING
def self.load_and_authorize_resource!
#classa = #stubb_classA
returns true
end
end
CanCan::ControllerResource.any_instance.responds_like(MOCKING)
NoMethodError: undefined method `responds_like' for Mocha::ClassMethods::AnyInstance:0x00000006dfe958>

If one takes a look at the implementation, it ends up calling find_resource, which uses an adapter pattern to find the appropiate instance.
I got it to work by stubbing an active_record class find method, be aware cancancan also has a find_by method, but in my case it worked with just stubbing find.
first you should stub authorization, it can vary widely, in my case, the authorization was:
ability.rb
can :manage, ClassA do |w|
w.user_workspaces.exists?(admin: true, user: user)
end
so, because cancancan asks the object for authorization, the stub should be
#stubbed_object.user_workspaces.stubs(:exists?).with(:admin => true, :user =>#user).returns(true)
Finally, stub the finder
#make ClassA return a stubbed object.
ClassA.stubs(:find).with(#stubbed_object.id).returns(#stubbed_object)
By the way, if someone's got an alternative, please post it.

Related

Rspec: stubbed model method still being called in controller test

In have this model in Rails:
class User < ActiveRecord::Base
def self.create_auth_from_hash(hash)
# stuff
end
end
I want to stub the create_from_auth_hash method so I can isolate the controller behaviour. Apparently the current syntax for this is:
expect_any_instance_of(User).to receive(:create_from_auth_hash).and_return(false)
But even though I get no errors, the model method is still called.
How can I stub model methods? I'm using Rails 4.1 and RSpec 3.0.
Your expectation is set up to stub a method on any instance of User, not the class method you've defined on User.
To do that, you just pass User as the argument to expect, instead of an instance. Like this:
expect(User).to receive(:create_from_auth_hash).and_return(false)

Can't use `find_by` method while `where` works in ActiveRecord::Base

I have a method like this, that works fine.
class User < ActiveRecord::Base
def self.find_or_create_from_twitter_id(twitter_id)
user = where(twitter_id: twitter_id).first
user ||= create_from_twitter_id(twitter_id)
end
end
Then I changed where(...).first to find_by, because I thought the two expression are basically same.
def self.find_or_create_from_twitter_id(twitter_id)
user = find_by(twitter_id: twitter_id)
user ||= create_from_twitter_id(twitter_id)
end
But I get undefined method `find_by' for #<Class:0x007fbb2b674970> error when I try to create a User.
I have no idea why find_by doesn't work here. I'll be very grateful if you tell me what is wrong.
find_by method is introduced in Rails 4. If you're using Rails 3.x or lesser version then use: find_by_<attribute_name> instead:
find_by_twitter_id(twitter_id)
But, then there's another method which find and create by the attributes passed to it, which you can use if it fits your needs:
find_or_create_by_twitter_id(twitter_id)

Mocking/stubbing a method that's included from "instance.extend(DecoratorModule)"

I use a decorator module that get's included in a model instance (through the "extends" method). So for example :
module Decorator
def foo
end
end
class Model < ActiveRecord::Base
end
class ModelsController < ApplicationController
def bar
#model = Model.find(params[:id])
#model.extend(Decorator)
#model.foo
end
end
Then I would like in the tests to do the following (using Mocha) :
test "bar" do
Model.any_instance.expects(:foo).returns("bar")
get :bar
end
Is this possible somehow, or do you have in mind any other way to get this functionality???
Just an Assumption Note: I will assume that your Decorator foo method returns "bar" which is not shown in the code that you sent. If I do not assume this, then expectations will fail anyway because the method returns nil and not "bar".
Assuming as above, I have tried the whole story as you have it with a bare brand new rails application and I have realized that this cannot be done. This is because the method 'foo' is not attached to class Model when the expects method is called in your test.
I came to this conclusion trying to follow the stack of called methods while in expects. expects calls stubs in Mocha::Central, which calls stubs in Mocha::ClassMethod, which calls *hide_original_method* in Mocha::AnyInstanceMethod. There, *hide_original_method* does not find any method to hide and does nothing. Then Model.foo method is not aliased to the stubbed mocha method, that should be called to implement your mocha expectation, but the actual Model.foo method is called, the one that you dynamically attach to your Model instance inside your controller.
My answer is that it is not possible to do it.
It works (confirmed in a test application with render :text)
I usually include decorators (instead of extending them at runtime) and I avoid any_instance since it's considered bad practice (I mock find instead).
module Decorators
module Test
def foo
"foo"
end
end
end
class MoufesController < ApplicationController
def bar
#moufa = Moufa.first
#moufa.extend(Decorators::Test)
render :text => #moufa.foo
end
end
require 'test_helper'
class MoufesControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "bar" do
m = Moufa.first
Moufa.expects(:find).returns(m)
m.expects(:foo).returns("foobar")
get :bar, {:id => 32}
assert_equal #response.body, "foobar"
end
end
Ok, now I understand. You want to stub out a call to an external service. Interesting that mocha doesn't work with extend this way. Besides what is mentioned above, it seems to be because the stubbed methods are defined on the singleton class, not the module, so don't get mixed in.
Why not something like this?
test "bar" do
Decorator = Module.new{ def foo; 'foo'; end }
get :bar
end
If you'd rather not get the warnings about Decorator already being defined -- which is a hint that there's some coupling going on anyway -- you can inject it:
class ModelsController < ApplicationController
class << self
attr_writer :decorator_class
def decorator_class; #decorator_class ||= Decorator; end
end
def bar
#model = Model.find(params[:id])
#model.extend(self.class.decorator_class)
#model.foo
end
end
which makes the test like:
test "bar" do
dummy = Module.new{ def foo; 'foo'; end }
ModelsController.decorator_class = dummy
get :bar
end
Of course, if you have a more complex situation, with multiple decorators, or decorators defining multiple methods, this may not work for you.
But I think it is better than stubbing the find. You generally don't want to stub your models in an integration test.
One minor change if you want to test the return value of :bar -
test "bar" do
Model.any_instance.expects(:foo).returns("bar")
assert_equal "bar", get(:bar)
end
But if you are just testing that a model instance has the decorator method(s), do you really need to test for that? It seems like you are testing Object#extend in that case.
If you want to test the behavior of #model.foo, you don't need to do that in an integration test - that's the advantage of the decorator, you can then test it in isolation like
x = Object.new.extend(Decorator)
#.... assert something about x.foo ...
Mocking in integration tests is usually a code smell, in my experience.

custom method_missing ignored if instance is defined via an ActiveRecord association

I have submissions that might be in various states and wrote a method_missing override that allows me to check their state with calls like
submission.draft?
submission.published?
This works wonderfully.
I also, for various reasons that might not be so great, have a model called Packlet that belongs_to a meeting and belongs_to a submission. However, I was surprised to find that
packlet.submission.draft?
returns a NoMethodError. On the other hand, if I hard-code a #draft? method into Submission, the above method call works.
How do I get my method_missing methods to be recognized even when the instance is defined via an ActiveRecord association?
Have you added the draft? method to your respond_to? method for that object? My guess would be that the issue might arise there. What happens when you type:
submission.respond_to?(:draft?)
To fix this, actually write a respond_to? method like this:
def respond_to?(method, include_priv = false) #:nodoc:
case method.to_sym
when :draft?, :published?
true
else
super(method, include_priv)
end
end
My recommendation would be to implement this without using method_missing instead though, so by doing some meta-programming like this:
class Submission
[:draft, :published].each do |status|
define_method "#{status}?" do
status == "#{status}?"
end
end
end

Difficulty aliasing `is_x?` to `has_role? x`

Each user has many roles; to find out whether a user has the "admin" role, we can use the has_role? method:
some_user.has_role?('admin')
Which is defined like this:
def has_role?(role_in_question)
roles.map(&:name).include?(role_in_question.to_s)
end
I'd like to be able to write some_user.has_role?('admin') as some_user.is_admin?, so I did:
def method_missing(method, *args)
if method.to_s.match(/^is_(\w+)[?]$/)
has_role? $1
else
super
end
end
This works fine for the some_user.is_admin? case, but fails when I try to call it on a user referenced in another association:
>> Annotation.first.created_by.is_admin?
NoMethodError: undefined method `is_admin?' for "KKadue":User
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.4/lib/active_record/associations/association_proxy.rb:215:in `method_missing'
from (irb):345
from :0
What gives?
Rails checks if you respond_to? "is_admin?" before doing a send.
So you need to specialize respond_to? also like:
def respond_to?(method, include_private=false)
super || method.to_s.match(/^is_(\w+)[?]$/)
end
Note: Don't ask me why rails checks for respond_to? instead of just doing a send there, I don't see a good reason.
Also: The best way (Ruby 1.9.2+) is to define respond_to_missing? instead, and you can be compatible with all versions with something a bit fancy like:
def respond_to_missing?(method, include_private=false)
method.to_s.match(/^is_(\w+)[?]$/)
end
unless 42.respond_to?(:respond_to_missing?) # needed for Ruby before 1.9.2:
def respond_to?(method, include_private=false)
super || respond_to_missing?(method, include_private)
end
end
The ActiveRecord::Associations::AssociationProxy class overrides method_missing and intercepts the call you are looking for before it gets to the model.
This happens because AP checks if the model respond_to? the method, which in your case, it doesn't.
You have a few solutions aside from editing Rails' source:
First, manually define each of the is_* methods for the user object using metaprogramming. Something like:
class User
Role.all.each do |role|
define_method "is_#{role.name}?" do
has_role?(role.name)
end
end
end
Another is to load the User object via some other means such as
User.find(Annotation.first.user_id).is_admin?
Or use one of the other answers listed.

Resources