Why Rails Instance Method Can Be Used as Class Method in Rspec

Why Rails instance method can be used as class method in rspec

It's not an rspec thing, it's an ActionMailer thing. Looking at:

https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb

Take a look at the comments in lines 135-146:

# = Sending mail
#
# Once a mailer action and template are defined, you can deliver your message or defer its creation and
# delivery for later:
#
# NotifierMailer.welcome(User.first).deliver_now # sends the email
# mail = NotifierMailer.welcome(User.first) # => an ActionMailer::MessageDelivery object
# mail.deliver_now # generates and sends the email now
#
# The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call
# your method to generate the mail. If you want direct access to delegator, or <tt>Mail::Message</tt>,
# you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object.

The functionality is implemented by defining a method_missing method on the ActionMailer::Base class that looks like:

  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

Essentially, defining a method on an ActionMailer instance (NotifierMailer in the comment example) and then calling it on the class creates a new MessageDelivery instance which delegates to a new instance of the ActionMailer class.

Rspec passes class method but fails instance method

Solution: Need to put 'cash.reload' after 'Stock.change_to_error' and before the expect line.

When using let! the object is created before the test. Updating the underlying data outside the object causes the instance to be outdated. Calling reload on it forces ActiveRecord to refresh it from the database.


When you use let, RSpec does not call the block until the first time you reference the attribute, in this case, cash. So in your first example, you're running change_to_error on no records at all and then checking the status on cash, a record that gets created on the line with expect. In your second example, the cash object is created, then changed to an error. I'd recommend tailing your log to confirm this (tail -f log/test.log)

If you change to let!, RSpec will create the object before every example is run. Another alternative is to reference cash in your example before calling change_to_error on all records that are created.

RSpec test that a class method calls an instance method

You can use stubs on the method by which .foo gets the instance.

For instance:

describe '.foo' do
let(:job) { create :job }
it 'calls job.bar' do
Job.stub(:find).and_return job
job.should_receive(:bar)
Job.foo
end
end

What this does is ensures that the instance that you expect to have methods called on is the one that actually gets used by .foo.

You can add expectations or argument matchers to this, so:

Job.should_receive(:find).with(job.id).and_return(job)

RSpec - mock a private class method call inside a request spec?

You're mocking on the class, which is how you mock you "static" class-level methods. For example, if your method was def self.foo and you called it via MyClass.foo, then allow(MyClass) is the way to go.

Your method is not a class-level method, it's an instance method. You invoke it by first creating an instead of MyClass and then calling the method on that instance. You need to use allow_any_instance_of to mock the method for all future instances of the class:

allow_any_instance_of(MyClass).to receive(....)

Test that instance method calls class method

If you don't really care what the class method is doing, and only that it gets called, you can do something like:

describe Test do
context '#instance_method' do
it 'should call the class method' do
Test.should_receive(:class_method)
Test.new.instance_method
end
end
end

RSpec - stubbing an instance method

You were close :)

allow_any_instance_of(Event)
.to receive(:finishes_after_venue_shuts?)
.and_return(false)

But using allow_any_instance_of is considered a bad practice, so more appropriate would be using a double:

let(:event) { instance_double(Event, finishes_after_venue_shuts?: false) }

allow(Event).to receive(:new).and_return(event)


Related Topics



Leave a reply



Submit