I need to ensure that running an importer results in sending out an email.
This is what I got so far:
describe '#import' do
it 'triggers the correct mailer and action', :vcr do
expect(OrderMailer).to receive(:delivery_confirmation).with(order)
Importer.new(#file).import
remove_backed_up_file
end
end
It fails with:
pry(#<ActiveRecord::ConnectionAdapters::TransactionManager>)> error
=> #<NoMethodError: undefined method `deliver_now' for nil:NilClass>
Which obviously can't work out as I am expecting the Mailer class to receive the (instance) method call. But how can I get a hold of the mailer instance that will receive the call? How would you test that a unit's method triggers a certain mailer?
I assume the delivery_confirmation method in reality returns a Mail object. The problem is that ActionMailer will call the deliver method of the mail object. You've set an expectation stubbing out the delivery_confirmation method but you haven't specified what should be the return value. Try this
mail_mock = double(deliver: true)
# or mail_mock = double(deliver_now: true)
expect(mail_mock).to receive(:deliver)
# or expect(mail_mock).to receive(:deliver_now)
allow(OrderMailer).to receive(:delivery_confirmation).with(order).and_return(mail_mock)
# the rest of your test code
If I got you right,
expect_any_instance_of(OrderMailer).to receive(:delivery_confirmation).with(order)
will test the mailer instance that will receive the call.
For more precision you may want to set up your test with the particular instance of OrderMailer (let's say order_mailer) and write your expectation the following way
expect(order_mailer).to receive(:delivery_confirmation).with(order)
Related
I've created a new mail on rails 5 using the mailer generator:
$ rails g mailer mymailer message
Rails created the application_mailer, mymailer_mailer, views and tests. Ok.
This is the mailer generated by rails:
class MymailerMailer < ApplicationMailer
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.mymailer_mailer.message.subject
#
def message
#greeting = "Hi"
mail to: "to#example.org"
end
end
But whenever I've tried to send the mail I've got the following error:
NoMethodError: undefined method `reject!' for nil:NilClass
After spent about two hours double-checking every config file I've decided to change method to bla...
VoilĂ : It worked, Ok! But why?
BTW: The message method I've found is from ActionMailer::MessageDelivery but there's no mention on Rails Guides of that.
If you look at the docs for MessageDelivery, there appears to be a method already provided named message which
Returns the resulting Mail::Message
My assumption is that your definition is overriding this provided method, but you are not returning the expected Mail::Message type object.
As another answer stated, there's already a method in the class named message. This shouldn't be a problem if you use the class as intended, since the mailer shouldn't have a single message named "message", it should have a more descriptive name.
The intent of a Mailer object is to define a context for messages that may be sent.
So for example, a UserMailer would be used to build messages to a user. Then each different type of message has a method, such as forgotten_password or welcome.
The documentation includes a more thorough example that follows this.
Question: Why is the method undefined if it's just there?
Details:
I have a very simple mailer class:
class ProductMailer < ApplicationMailer
def sample_email
mail(to: "me#example.com") # I hardcode my own email just to test
end
end
And a very simple call from ProductsController:
def sample_email
ProductMailer.sample_email().deliver_later
redirect_to #product, notice: 'Email was queued.'
end
The email fails to be sent. I am using Sidekiq to process emails in background. The Sidekiq Web UI shows failed jobs in the Tries page and I can see why it failed:
NoMethodError: undefined method `sample_email' for ProductMailer:Class
I tried to rename the method and restart the server with rails server but none of that removes the error. I am not using any namespaces.
Question: Why is the method undefined if it's just there?
Note: I found out by chance that the method is found if I name it notifybut maybe that's because I'm overwriting some method from ActionMailer base class, I don't know.
Answer: Restart Sidekiq
I created the mailer class before starting Sidekiq, but I renamed the sample_email method while Sidekiq was already running, so it seems that Sidekiq doesn't recognize new methods on-the-fly.
I renamed the method because I am used to development environment, where you can change almost anything on the fly...
It's because you've defined an instance method, and then you try to call it on a class. Change it to
def self.sample_email
....
I'm using rspec 3.0.3 and ruby 2.1.2 and just can't figure out what's going wrong.
Sorry for not good code implementation (I mean that class variables), but it was the easier way to show whats going wrong.
I have 2 classes. First calling new_method of the Test class should call AnotherTest.call_method that should change ##c_var class variable.
require "rspec"
class Test
def self.new_method
AnotherTest.call_method
end
end
class AnotherTest
##c_var = "hola"
def self.call_method
##c_var = "holla from another test"
end
def self.c_var
##c_var
end
end
And I'm writing specs for it:
describe Test do
it "should call method from an other class" do
Test.new_method
expect(AnotherTest.c_var).to be_eql("holla from another test")
end
end
And this specs is working OK. But then I'm trying to use "expect to receive call" something goes wrong
describe Test do
it "should call method from an other class" do
expect(AnotherTest).to receive(:call_method).and_return("holla from another test")
Test.new_method
expect(AnotherTest.c_var).to be_eql("holla from another test")
end
end
Failures:
1) Test should call method from an other class
Failure/Error: expect(AnotherTest.c_var).to be_eql("holla from another test")
expected `"hola".eql?("holla from another test")` to return true, got false
# ./test.rb:26:in `block (2 levels) in <top (required)>'
Seems like RSpec is making this check in something like a migration and doing a rollback after it.
It's such a strange sample, I know, but I've noticed this bug only then method of one instance of a class is calling method from the other instance and that method is trying to change something.
By using expect(...).to receive(...), the original method is not being called. Rather, when it is called, it just returns whatever you passed into and_return(...) without actually executing your method.
What you probably want is and_call_original. This way, you can ensure the method is called, and still allow it to execute:
expect(AnotherTest).to receive(:call_method).and_call_original
Source: https://www.relishapp.com/rspec/rspec-mocks/v/3-0/docs/configuring-responses/calling-the-original-implementation
I'm trying to write a test for my Message model. This model has a method 'send_message':
def send_message
ContactMailer.contact_mail(self.name, self.email, self.text, self.ip).deliver
end
In my rspec file I have the following:
mailer = double(ContactMailer)
mailer.should_receive(:contact_mail)
FactoryGirl.build(:message).send_message
I am receiving the following error:
Failure/Error: mailer.should_receive(:contact_mail)
(Double ContactMailer).contact_mail(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
Any idea? Could it be because I'm not taking .deliver into account?
No, the issue is that you haven't told RSpec that ContactMailer should receive that message. The argument to double is just a way to "name" the double for documentation purposes, as discussed in https://github.com/rspec/rspec-mocks
You actually don't need a double in this case, as you can set the expectation directly on the ContactMailer class as follows:
ContactMailer.should_receive(:contact_mail)
Let's say I've called an actionmailer method which resides in a mailer.
I do the processing to determine if there are to be any recipients of the email in this method. Sometimes there are none, but it appears that once this method is called, it is impossible to abort sending. Finishing the method without setting a recipient throws an error. Is that correct? Is there no way to abort?
Thanks,
Chris.
This is the type of case where you should consider raising your own exception. You can define it in lib, and then catch it in the code that calls your mailer.
class AbortMailingException < Exception
end
# In your mailer ...
if !have_enough_recipients() # Or whatever conditions / checks you want to perform.
raise AbortMailingException.new
end
# In the code that calls your mailer ...
begin
my_mailer_function(args)
rescue AbortMailingException => e
# Handle error, log, ignore, whatever
end