Sending emails in test mode with ActionMailer in rails 3 - ruby-on-rails

I am having a slightly odd problem with sending mail in test mode with Rails 3
It seems that my mailers are not returning anything. For example I have a mailer called UserMailer. Users can make changes that require approval in the app so this has a method called changes_approved that should send the user an email notifying them that their changes have been approved.class
UserMailer < ActionMailer::Base
default :from => "from#example.com"
def changes_approved(user, page)
#user = user
#page = page
mail(:to => user.email, :subject => "Your changes have been approved")
end
end
In my controller I have the following line
UserMailer.changes_approved(#page_revision.created_by, #page_revision.page).deliver
However my tests fail at this point with the error:
undefined method `deliver' for nil:NilClass
When I trigger the same actions on the development site tho (http://localhost:3000 through a browser), the emails are sent out correctly and everything works quite happily
And to add further confusion, I am using devise for authentication and the emails for that seem to be working correctly both in test and development modes. Certainly I am not getting this same error and according to my email-spec tests, everythings working
So this leads me to believe that I have a problem with my mailers rather than my test mail config per se but I have no idea what. Any suggestions would be much appreciated
Thanks

I used https://gist.github.com/1031144
to convert
# Rails 2 method:
UserMailer.should_receive(:deliver_signup)
to
# Cumbersome Rails 3 method:
mailer = mock
mailer.should_receive(:deliver)
UserMailer.should_receive(:signup).and_return(mailer)

I had a similar problem - probably the UserMailer.changes_approved method is being replaced with a mock method, which returns nil (I wasn't using shoulda for that test, but that's my best guess).
My code looked like this (modified to use your example):
UserMailer.expects(:changes_approved).once
I fixed it with an additional stub:
#mailer = stub(:deliver)
UserMailer.expects(:changes_approved).once.returns(#mailer)
The nil is now replaced with #mailer.

To test the delayed action mailer we need to first change the configuration of delayed_job (in config/initializers/delayed_job_config.rb) to
Delayed::Worker.delay_jobs = !Rails.env.test?
and in your tests the expectation should be set to
mock_mail = mock(:mail)
mock_mail.should_receive(:deliver)
UserMailer.should_receive(:changes_approved).with(user, page).and_return(mock_mail)

Well I have found the answer,
it looks like the problem was in the way I was testing these mailers. In each of the controller tests I had a line similar to
UserMailer.should_receive(:changes_approved).with(user, page)
Whilst this test was passing fine, it appeared to break the mailer itself. I have removed this line from the tests and now they pass ok. Subsequent tests against ActionMailer::Base.deliveries.last to check the details of the sent email are correct appear to be ok so I am happy that this line is not neccessary.
If anyone has an explanation as to why this breaks tho, I would be interested to find out
Thanks anyways

Related

Rails 6 & deliver_later doesn't affect ActionMailer::Base.deliveries

After upgrading to Rails 6 I am noticing that default mailer's .deliver_later is not working the same as in Rails 5.
Configuration:
config.active_job.queue_adapter = :inline
When running Mailer.register_email(...).deliver_later - nothing is stored in ActionMailer::Base.deliveries. This array gets filled if I run perform_enqueued_jobs - it seams like queue_adapter = :inline doesn't work the way I expect it to work.
If I run Mailer.send(...).deliver_now then ActionMailer::Base.deliveries has proper value in it.
Any idea why this is happening and how to solve this?
I had same problem in my tests. A search on the Internet yielded nothing, so I started experimenting.
I tried wrapping the call method of sending mail in
assert_emails 1 do
Mailer.register_email(...).deliver_later
end
After that, ActionMailer::Base.deliveries populated correctly.
If the exact number of emails could easily change, this is another option:
assert_changes 'enqueued_jobs.size' do
# Some code that sends email with deliver_later
end
This allows you to test that emails were sent but it disregards the exact number (which is a limitation of the asserts_emails method - other than this, the asserts_emails method is great).
I found that the enqueued_jobs method is very helpful in testing anything background jobs, including deliver_later
NOTE: the above example only checks that the enqueued jobs list was changed. If you want to be more specific and check that the queue was changed with emails, you should do this:
assert_changes 'enqueued_jobs.select {|job| job["job_class"] == "ActionMailer::MailDeliveryJob"}.size' do
# Some code that sends email with deliver_later
end
The issue
The issue lies in two new lines of code added to Rails 6 (line 1 & line 2),
where basically, the callback before_setup defined here (in RSpec) and here (in Minitest) gets overridden (by this), thus forcing the queue_adapter to be the test adapter instead of the one defined by config.active_job.queue_adapter.
Workaround
So in order to use the queue_adapter defined by config.active_job.queue_adapter and therefore restore the Rails 5 behaviour we can do something like the below.
# spec/support/active_job/test_helper.rb
module ActiveJob
module TestHelper
def before_setup
super
end
end
end

Is there any way to prevent Rails autocleaning class variables when testing emails?

I testing a Rails application that sends emails in some situations. It's an API.
For the testing, I'm using the Airborne gem, which makes API testing pretty easy. All went correct except when I had to test the email deliveries. I tried the following:
it "blah" do
//Code that makes my API send an email
puts ActionMailer::Base.deliveries.inspect
end
But deliveries array is always empty. I also tried with Emails.deliveries.inspect. Emails is my custom Mailer that inherits ActionMailer::Base.
I ended reading the API documentation of ActionMailer and met the interceptor concept. Interceptors doesn't work in :test delivery method so I switched to :smtp. In fact, the emails are being sent correctly, but I can not access them on the tests to make expectations.
My interceptor code is this right now
initializers/email_interceptor.rb
class EmailInterceptor
##msgs = []
def self.delivering_email(message)
puts message
//Rails.logger.debug "Email being sent: " + message.to_s
##msgs << message
Rails.logger.debug "Actual messages array: #{##msgs}"
end
def self.msgs
##msgs
end
end
ActionMailer::Base.register_interceptor(EmailInterceptor)
All OK. The debug messages print the array being populated correctly. But the variable is cleaned before my test statement is executed.
EDIT: The code above is executed when I run my test suite. But the variable is empty accessed from the test itself.
//test code
puts EmailInterceptor.msgs.inspect
=> []
Is there any way to prevent this behavior?
You may have config.action_mailer.perform_deliveries = false in your test.rb config. It seems like you should really be using config.action_mailer.delivery_method = :test since this will allow ActionMailer::Base.deliveries to be populated, which makes for easier and more reliable testing. Do you really need interceptors for your tests?

Why are emails not getting in the ActionMailer::Base.deliveries table?

Currently in my development environment I am not seeing anything in the ActionMailer::Base.deliveries table after mail is used. Is there a setting I am missing?
config/environments/development.rb: config.action_mailer.delivery_method = :test.
app/mailers/notifier.rb
def send_email(user)
mail(:to => user.email, :subject => "Welcome to our website!")
end
These are the tests I'm running:
When /^I signup$/ do
visit signup_path
#user = FactoryGirl.build(:user)
fill_in "user_email", :with => #user.email
fill_in "user_password", :with => #user.password
click_button "Join now"
end
Then /^I should receive a confirmation email$/ do
email = ActionMailer::Base.deliveries.last
email.subject.should == "Welcome to our website!"
end
Right now I get the following error for the I should receive a confirmation email step:
undefined method subject for nil:NilClass (NoMethodError)
Thanks!
Another possibility might be that in config/environments/test.rb you have
config.action_mailer.delivery_method = :test
but you have set
config.action_mailer.perform_deliveries = false
so changing to
config.action_mailer.perform_deliveries = true
made it work again in my case.
Old question, I know, but a couple things come to mind:
You talk about the configuration of your development environment (config/environments/development.rb), but it's your test that isn't working. Check config/environments/test.rb and maybe config\application.rb.
I actually wanted to see the email array in my development environment, but it always returned nil in the console. I finally realized that because the array is just a memory object, not in the database, the console (rails c) can't see what's going on in the server (rails s). When I added <%= ActionMailer::Base.deliveries.count %> to a view, I could immediately see the array getting populated in development.
Another "I can't see that process from the console" situation arises if you are using a separate thread or task for sending emails. With delayed_job, I found this answer suggesting you can look at the job (not the actual email) using Delayed::Job.last.handler, which does look at the database so works across processes. However this only works while the job is still in the database, i.e. before a worker has processed it.
The error you are getting says that email is nil. In other words, the deliveries.last method is returning nil because there are no emails in it.
There could be a number of reasons... I'm assuming that you are using cucumber as the testing mechanism?
1. Verify that 'click_button' submits
You could try to put puts or log statements. Make sure that when you run the cucumber feature, it actually prints. Sometimes these things don't work because the front-end logic doesn't work and the test is failing correctly. In other words, the controller endpoint you are expecting to be triggered isn't being triggered.
2. Verify that 'send_email' is right - using a unit test
Next, verify that you are actually sending email. Write a unit test to verify this works. This should be quick and easy to get going. Plenty of articles out there.
Lastly, not really related, but you might want to roll with email-spec which should provide some nice testing methods so you don't have to reinvent the wheel.
After making the changes answered by #Dominik Steiner, if it still didn't work ,
another possibility might be that in config/initializers/mailer.rb, you should have :
ActionMailer::Base.delivery_method = :test
Above was missing in my case and it worked now.

Why does my UserMailer in dev mode not output Rails.logger.info?

I need to debug using Rails.logger.info in my user_mailer.rb
Any idea why the Rails.logger.info is not outputting in the user_mailer but it does everywhere else?
Thanks
According to this, it should work just fine within a Mailer.
To write in the current log use the logger.(debug|info|warn|error|fatal) method from within a controller, model or mailer
You could always just debug using a puts statement instead and check the logs for the output.
puts "I am inside the Mailer method"

In Rails 3, I'm getting a NoMethodError for the recycle! method during testing

I've got a functional test that's using fixtures. I also am using fixtures in my Unit tests, but they work without flaw. When running the functional tests, I get a:
NoMethodError: undefined method 'recycle!' for #<Response:0x10346be10>
/test/functional/responses_controller_test.rb:10:in 'test_testing'
My functional tests, at this point, are doing nothing more than a get to the index action. Example:
setup do
#response = responses(:one)
end
test "testing" do
get :index
assert true
end
My TestHelper class does include all fixtures, so the Responses fixtures are definitely loading. And like I said, the fixtures work perfectly fine in Unit tests.
Any idea what might be causing this?
Change
setup do
#response = responses(:one)
end
to
setup do
#myresponse = responses(:one)
end
and off you go!
The problem lies in "actionpack-3.0.1/lib/action_controller/test_case.rb" around line 386.
The testcase assumes that #response holds a "ActionDispatch::Response". You overwrite it, it's no "ActionDispatch::Response" anymore and the testcase fails.
I'm not sure if this is intended behaviour.
Anyway, just make sure you don't overwrite #response/#request/#controller/#routes and it should work.
Flo's answer is mostly correct, but I just wanted to clarify:
In Rails 3.0.x that #request is expected to be an ActionController::TestRequest type, which has the recycle! method defined on it. There's TestResponse, TestRequest, and TestSession.
You might want to actually initialize or manipulate objects of these types for certain situations. In my case, I needed to mock the site having a specific domain name with
#request = ActionController::TestRequest.new unless #request
#request.host = "www.hostname.com"
I just had a similar problem naming a variable #request.
I changed to #_request and it solved the problem.

Resources