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)
Related
I've got this helper which I'm trying to write tests for in Minitest. The helper calls another method depending on the object class I'm passing as an argument, like so:
def label_for(object)
status = object&.status
case object.class.name
when "Subscription"
class_for_subscription_status(status)
when "Payment"
class_for_payment_status(status)
when "Purchase"
class_for_purchase_status(status)
when "Invoice"
class_for_invoice_status(status)
when "Ticket"
class_for_ticket_status(status)
end
Each individual method is already tested somewhere else, so I just need to test that if I pass a class Subscription object to label_for, it will invoke class_for_subscription_status(status) and not something else.
This is the test I've come up with, but I get NoMethodError: undefined method ``class_for_subscription_status' for #<AuxiliariesHelperTest errors.
test "#label_for(object) should invoke the right helper if object is of class Subscription" do
AuxiliariesHelperTest.any_instance.stubs(:label_for).with(subscriptions(:user)).returns(:class_for_subscription_status)
assert_equal class_for_subscription_status(subscriptions(:user).status), label_for(subscriptions(:user))
end
What am I doing wrong?
Could you add the whole classes? Is a little bit hard to guess with just this snippet.
One of the problems I see is that you are stubbing a method from the AuxiliariesHelperTest class, instead of the AuxiliariesHelper class.
Another possible issue is that your helper seems to be a module and not a class, and you should include the helper in your test file. Or your test class should inherit from ActionView::TestCase. Something like this might help:
class AuxiliariesHelperTest < ActionView::TestCase
include AuxiliariesHelper
test "#label_for(object) should invoke the right helper if object is of class Subscription" do
AuxiliariesHelper.any_instance.stubs(:label_for).with(subscriptions(:user)).returns(:class_for_subscription_status)
assert_equal class_for_subscription_status(subscriptions(:user).status), label_for(subscriptions(:user))
end
end
Although in my opinion, you should not stub the method, but expect that the correct method is called:
class AuxiliariesHelperTest < ActionView::TestCase
include AuxiliariesHelper
test "#label_for(object) should invoke the right helper if object is of class Subscription" do
AuxiliariesHelper.any_instance.expects(:label_for).with(subscriptions(:user).status)
label_for(subscriptions(:user))
end
end
This question already has answers here:
How do I stub things in MiniTest?
(8 answers)
Closed 3 years ago.
Contrary to the documentation on .stubs, it seems that I'm able to stub a method that doesn't exist.
Considering the following code:
class DependencyClass
def self.method_to_be_stubbed
'hello'
end
end
class TestMethodClass
def self.method_being_tested
DependencyClass.method_to_be_stubbed
end
end
class StubbedMissingMethodTest < ActiveSupport::TestCase
test '#method_being_tested should return value from stub' do
assert_equal 'hello', TestMethodClass.method_being_tested
DependencyClass.stubs(:method_to_be_stubbed).returns('goodbye')
assert_equal 'goodbye', TestMethodClass.method_being_tested
end
end
In this example, DependencyClass.stubs(:method_to_be_stubbed).returns('goodbye') works as expected because #method_to_be_stubbed exists on DependencyClass. However, if I were to change #method_to_be_stubbed to a class instance method of DependencyClass as follows:
class DependencyClass
def method_to_be_stubbed
'hello'
end
end
class StubbedMissingMethodTest < ActiveSupport::TestCase
test '#method_being_tested should return value from stub' do
assert_equal 'hello', TestMethodClass.method_being_tested
# despite the method not existing on the class,
# instead on the instance - yet it still works?
DependencyClass.stubs(:method_to_be_stubbed).returns('goodbye')
assert_equal 'goodbye', TestMethodClass.method_being_tested
end
end
My stub for #method_to_be_stubbed maintains the class method on DependencyClass, despite it not existing any longer. Is the expected behaviour not for the .stubs call to fail, since the method being stubbed does not exist?
Is the expected behaviour not for the .stubs call to fail, since the method being stubbed does not exist?
No, expected behaviour is not to fail. Here's why. You're not stubbing a method. You're stubbing a response to a message. For example, you have this line in your code: user.name. This means you're sending message age to object user. The easiest/most common way to teach user to handle message age is to ensure that it has an instance method called age. But there are other ways too. You could make user respond to age with method_missing. As far as ruby is concerned, this is just as valid.
Therefore, it would be wrong for minitest to check for method's existence here.
I would like to know is there any specific reason for doing this or is this a silly mistake done by someone (or is it something else that I am not understanding).
class SomeMailer < ActionMailer::Base
def first_method(user)
mail(to: user.email, subject: "Testing")
end
end
This method is called at some other place in the code as follows
SomeMailer.first_method(user).deliver
ActionMailer::Base classes are weird... Yes, you do indeed call instance methods on the class - which obviously won't work for 'normal' classes!
But there's some meta-programming magic under the hood:
module ActionMailer
class Base < AbstractController::Base
def method_missing(method_name, *args) # :nodoc:
if action_methods.include?(method_name.to_s)
MessageDelivery.new(self, method_name, *args)
else
super
end
end
end
end
If you look through the rails documentation, you'll see that calling instance methods on the class is, strangely, the normal thing to do for mailers.
This is how rails is intended to work.
It is also mention in rails guides that
You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
Rails do the internal processing by invoking method_missin.
Basically, any action method defined in mailer class will be intercepted by method_missing and will return an instance of MessageDelivery, otherwise it runs the default implementation. And where do action methods come from? ActionMailer::Base inherits from AbstractController::Base, so it works exactly the same as for controllers - it returns a set of public instance methods of a given class.
Rails itself encourage this behavior. For more information, you can refer this link
I will try to answer this since I have come across similar situations myself while working on existing code.
The instance methods like this in a class help when you do call backs on a class. For example, if you want to perform some action on a object that was created from that class.
Say you have another class User and you want to send an email to a user immediately after creating a new user. In that case you can call this method on that object by doing
after_save :method_name
I have a class method in my User model:
def self.method_name
...
end
In a controller, I need to call this method on a User instance obtained through association:
#user = game_play.player.user
As expected, it threw a no method error because it's a class method.
What is the way to call the method in this case?
EDIT: Adding code for question clarification
#user = #game_play.client.user.
#token = #user.set_login_bypass_token
My model:
def set_login_bypass_token
#We generate a raw token and an encrypted version of the same token
raw, enc = Devise.token_generator.generate(User, :login_bypass_token)
self.login_bypass_token = enc
self.login_bypass_token_set_at = Time.now
self.save(validate: false)
#Raw token is sent to the user via email to provide auto-login
raw
end
The error:
PG::UndefinedColumn: ERROR: column users.login_bypass_token does not exist
Notice the error has it as users.login_bypass_token instead of set_login_bypass_token
EDIT:
My first answer was before you mentioned Devise and assuming you didn't know if you needed a class or instance method. It's clear that your method must be an instance one.
I think you are trying to apply something you found here: https://stackoverflow.com/a/30857087/3372172
This requires that you add a new field to the users database, to manage the :login_bypass_token. Because you will use this column later to perform a find_by. Devise does not add this column to the database.
PREVIOUS ANSWER
If the method needs to access instance variables (which means it acts differently depending the specific object in the User class), it should be an instance method, defined without the self keyword.
If it is a class method, it cannot depend on any attribute from a specific object, and you cannot call it from an instance of the class.
You must decide if it's really a class method or an instance method.
`
If you need a class method to be called from an instance, you can do this (but I don't know why you could need it).
class User
def self.method_name
# blablabla
end
def method_name
User.method_name
end
end
Should be using an instance method instead of a class method. I'm not sure how you would get an error where it's looking for an attribute on the model since those can only be defined in the schema. If you're adding a regular instance method within the model it should work correctly.
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.