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
Find a Unique Element in a Compound Array
Connecting to Google Analytics API in a Rails App
Module and Class with the Same Name in Rails Project
How to Create This File Input and Output Assignment in Ruby
Watir Browser Process Doesn't Start Properly (Windows)
Undefined Method 'Click' for Nil:Nilclass (Mechanize)
Use Variable in Parameter Ruby on Rails
When Trying to Run Rspec, I Get "Uninitialized Constant Activemodel"
Ror: Execute SQL in Controller
Finding Parenthesis via Regular Expression
Extract Text Between Two Tags Using Regex in Ruby
How to Replace Text in a Ruby String
Ror: How to Get My Microposts to Show Up
Create Multiple Records with Fields_For - Rails
+= Operator Overloading in Ruby