Problem
I seem to be facing a stubborn issue with my RSpec tests trying to constantly send emails in test-env despite my configuration should avoid it. Whatever I try it seems to totally ignore it.
My environment
Rails 6.1.1
Ruby 3.0.0
sendgrid-ruby gem 6.3.9
I have a mailer class inheritance-chain as follows: OrganizationMailer<-ApplicationMailer<-ActionMailer::Base
In my config/environments/test.rb I have the following mail-related configuration
config.action_mailer.delivery_method = :test
config.action_mailer.perform_deliveries = false
config.active_job.queue_adapter = :test
My config/application.rb and config/environment.rb don't contain any extra configuration.
Bit offtopic maybe, but just in case adding it as well:
I have ensured that the line ENV['RAILS_ENV'] ||= 'test' is present in both- spec/spec_helper.rb and spec/rails_helper.rb since for some reason Rails randomly triggered my tests in staging environment. I also had to change the .rspec file contents in my project from --require rspec_helper to --require rails_helper. I got the solution from here1, here2 and here3. But this I don't think plays any role in current problem.
Bad solution
I hacked my way through this issue atm by just adding the unless Rails.env.test? on top of each email-sending method I'm having to ensure none of them reach Sendgrid in tests, but this sucks big time I know. That's why I'm posting this question to get this fixed properly without such if/unless-clauses.
My theory
Can it be that sendgrid-ruby is here to blame? I inherit things from ApplicationMailer and ActionMailer but eventually what is sending the emails is Sendgrid ruby gem. If so, how to avoid it best? I didn't find any hints from the sendgrid-ruby documentation about that. I will post one of my simple mailer-methods below so you could see the situation atm:
# frozen_string_literal: true
# using SendGrid's Ruby Library
# https://github.com/sendgrid/sendgrid-ruby
require 'sendgrid-ruby'
class MyMailer < ApplicationMailer
include SendGrid
def my_mailer_method(my_object:)
unless Rails.env.test? # <---- Hack I'd like to get rid of
from = Email.new(email: 'no-reply#my.domain', name: t('general.title'))
to = Email.new(email: my_object.contact_email)
subject = "#{t('my_mailer.my_mailer_method.subject')}: #{my_object.my_object_title}"
content = Content.new(
type: 'text/html',
value: ApplicationController.render(
template: 'my_mailer/my_mailer_method',
locals: {
my_object: my_object
},
layout: nil
)
)
mail = SendGrid::Mail.new(from, subject, to, content)
sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
# Send out mail
response = sg.client.mail._('send').post(request_body: mail.to_json)
end
end
end
The issue here is that you may have set config.action_mailer.delivery_method = :test but you are not actually using ActionMailer to deliver your emails. Instead, within the MyMailer class you are directly using the SendGrid API, and sidestepping ActionMailer entirely.
If you want to use SendGrid API to send your emails, then I actually recommend using the sendgrid-actionmailer gem. It allows you to use ActionMailer to build your emails and uses the SendGrid API under the hood to send them. This allows you to send other parameters that the API supports and would be more difficult or impossible with SMTP, while still using the Rails standard ActionMailer to send the emails.
To ensure that your mails are sent by SendGrid in production, but not sent in test, you would set:
config.action_mailer.delivery_method = :sendgrid_actionmailer
in your production.rb environment. And set:
config.action_mailer.delivery_method = :test
config.action_mailer.perform_deliveries = false
as you already have in your test.rb environment.
Related
I recently switched over a rails app to send mail through amazon ses. The sending of the mail works fine, however the tests are failing around the mail items with the following error:
TypeError:
can't convert Fixnum into String
# (eval):3:in 'send_raw_email'
I have the test environment mail settings set properly to :test
config.action_mailer.delivery_method = :test
I can prevent the errors from happening by not sending emails, but then I get errors about the mail not being delivered.
config.action_mailer.perform_deliveries = false
should_change -> { ActionMailer::Base.deliveries.size } do
result should have changed, but is still 0
Gem versions are below:
rails (3.2.12)
rspec (2.8.0)
cucumber (2.3.3)
aws-sdk (1.33.0)
Any suggestions on what is going on, or how to work around this. If any other info is needed let me know. This worked fine before I switched the sending of mail to use amazon ses.
EDIT:
The tests that are failing, are all that involve an email being sent using .deliver
BlahMailer.send_invite(team).deliver
This is inside a model and controller which the tests just call the associated route in the controller or call the associated function in the model. Not sure what else to show, since the error is showing its coming from send_raw_email which is the ses function, not any code from the app.
EDIT 2: More code
Here is one of the tests, in this example it sets up the factory for the model and each test.
before :each do
subject = Factory(:ticket, action_type: 'name_change', team_name: 'test123')
end
Here is the factory in rspec
Factory.define :ticket do |ticket|
team = nil
ticket.team { team = Factory(:team) }
ticket.creator { team.captain }
ticket.subject { Factory(:team_assignment, team: team, user: Factory(:active_user)).user }
end
And then here is the code in the model around where the email is sent
%w(requested approved cancelled accepted).each do |state|
define_method "notify_#{state}" do
TicketMailer.send("#{action_type}_#{state}", self).deliver
end
end
I have tried putting in straight text for basically everything having to do with the mailer, just to make sure there are no issues with that. If you need anymore of the model or the actual mailer let me know. I just really cant figure out where this is dieing. My only conclusion is its just a bug with the older versions of the aws sdk and rspec gems. I even tried replacing aws-sdk with newer versions but was still unsuccessful in getting anything else to work.
The problem is that you're setting config.action_mailer.perform_deliveries = false, so no deliveries are being made and therefore ActionMailer::Base.deliveries will always be an empty collection.
If you want to test email deliveries, then set config.action_mailer.perform_deliveries = true and use an interceptor to ensure you're not sending emails to users.
lass SandboxEmailInterceptor
def self.delivering_email(message)
recipients =
if Rails.env.test?
[
"user#example.com"
]
end
message.to = recipients
message.cc = []
end
end
unless Rails.env.production? || Rails.env.test?
ActionMailer::Base.register_interceptor(SandboxEmailInterceptor)
end
Rails 4.1.4 and Rspec 3
I'm doing a VERY basic email test with Rspec. If I call the mailer from rails console, it works perfectly. If I call it from the mailer spec I get:
wrong number of arguments (0 for 1..2)
The mailer is very basic:
def create_team_invite(org, email)
#organization = org
mail(:to=>email, :subject=>'Test Subject')
end
The Test is pretty basic too:
it 'can send out emails to invite others to create teams' do
UserMailer.create_team_invite(#org, 'test#test.com').deliver
expect(ActionMailer::Base.deliveries.count).to eq 1
mail = ActionMailer::Base.deliveries.first
expect(mail.subject).to eq 'Test Subject'
expect(mail.from).to eq 'test#test.com'
end
Its failing in the "mail(:to..." line in the mailer. Seems like maybe its some configuration issue in my environment, but I have Test setup exactly the same as Dev, using SMTP and sending it to a Mailcatcher port. I caught the exception and looked at the Backtrace, but don't see anything unusual...
Anyone seen this before?
Update: providing additional info that was requested.
My test.rb, minus comments:
Rails.application.configure do
config.cache_classes = true
config.eager_load = false
config.serve_static_assets = true
config.static_cache_control = 'public, max-age=3600'
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.action_dispatch.show_exceptions = false
config.action_controller.allow_forgery_protection = false
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.active_support.deprecation = :stderr
config.action_mailer.default_url_options = { :host => 'lvh.me:3000', :only_path=>false }
end
The entire rspec failure is:
UserMailer
Team Joining Email
can send out emails to invite others to create teams (FAILED - 1)
Failures:
1) UserMailer Team Joining Email can send out emails to invite others to create teams
Failure/Error: UserMailer.create_team_invite(#team, 'test#test.com').deliver
ArgumentError:
wrong number of arguments (0 for 1..2)
# ./app/mailers/user_mailer.rb:11:in `create_team_invite'
# ./spec/mailers/user_mailer_spec.rb:36:in `block (3 levels) in <top (required)>'
Finished in 0.25858 seconds (files took 29 minutes 48 seconds to load)
1 example, 1 failure
The way I configure my email is via an initializer that loads an email.yml file from my config, per environment. Exact same process used by both test and dev, with the same settings. (Again, I'm sending to Mailcatcher, instead of just to mail_delivery :test)
Update 2
I have narrowed it down to the Mailer missing the "request" object. If I dig through where the error is occurring (AbstractController rendering.rb, line 109) it tries to reference the request object:
if defined?(request) && request && request.variant.present?
This is calling over to Rack test.rb line 121:
def request(uri, env = {}, &block)
env = env_for(uri, env)
process_request(uri, env, &block)
end
So its like the Rack Test.rb class is being seen as the request method in that abstractcontroller... but I dont know how, or why, or why this is happening in this particular project...
I get this same exact error, and it seems that it's trying to call rack-test request code, when I'm just trying to test a mailer object. So what I did is just not include all the stuff that I put in the spec_helper for the mailer spec. That fixed the problem for me.
# require 'spec_helper' -> I removed this line,
# and manually require the files that's needed to just test the mailer object:
require 'rubygems'
require './config/environment'
require './app/mailers/your_mailer'
Note: I'm just doing a rack project using action_mailer, no rails stuff. So your solution will be different, but you get the idea.
Update:
After doing some more troubleshooting. I found this problem in my spec_helper.rb file
RSpec.configure do |config|
config.include include RackSpecHelper # notice the double include
...
end
# where RackSpecHelper is a custom module
module RackSpecHelper
include Rack::Test::Methods
# a bunch of other helper methods
end
Notice the double include on this line
config.include include RackSpecHelper
At first I tried just removing the line and my mailer test is running just fine. Suspicious with the double include, I remove the include so it's just like this
config.include RackSpecHelper
Now my mailer test runs just fine without having to do manual require like I posted earlier above (and it can run together with the other test that uses rack test stuff).
The double include is basically doing
config.include(include(RackSpecHelper))
which include the RackSpecHelper in the configure block, which loads all the methods in the top level namespace! (very bad thing). It works, but that means all the methods from Rack::Test::Methods are in the global namespace.
So I'm guessing in your case, you might have a line in the spec_helper that include Rack::Test::Methods in the global namespace like this
include Rack::Test::Methods
remove it and instead put it in the RSpec config like this
RSpec.configure do |config|
config.include RackSpecHelper
end
When I send an email with an attachment the data is logged in hex and fills up my whole log. Is there a way to disable logging of attachments?
I know I can disable mailer logging with config.action_mailer.logger = nil.
Unfortunately, the attachments are included in the logs if the logging level is set to :debug, the default level for non-production environments. This means that in production you should be fine, but your dev and staging environments could bloat during testing. You could turn down logging for your entire app (config.log_level = :info), but this is obviously less than ideal.
You can configure a custom logger:
config.action_mailer.logger = ActiveSupport::BufferedLogger.new("mailer.log")
config.action_mailer.logger.level = ActiveSupport::BufferedLogger::Severity::INFO
Rails 4
config.action_mailer.logger = ActiveSupport::Logger.new("mailer.log")
config.action_mailer.logger.level = ActiveSupport::Logger::Severity::INFO
This will split the log, but you can isolate the logging level change to the action mailer.
If you still want your log level to be debug, you can remove the attachments from the log output by overriding ActionMailer's LogSubscriber class. Look at the class in your actionmailer gem, and adjust accordingly. For my Rails 4.2.10 install, the relevant file is:
gems/actionmailer-4.2.10/lib/action_mailer/log_subscriber.rb
My module is:
module ActionMailer
class LogSubscriber < ActiveSupport::LogSubscriber
def deliver(event)
info do
recipients = Array(event.payload[:to]).join(', ')
"\nSent mail to #{recipients}, Subject: #{event.payload[:subject]}, on #{event.payload[:date]} (#{event.duration.round(1)}ms)"
end
debug { remove_attachments(event.payload[:mail]) }
end
def remove_attachments(message)
new_message = ''
skip = false
message.each_line do |line|
new_message << line unless skip
skip = true if line =~ /Content-Disposition: attachment;/
skip = false if skip && line =~ /----==_mimepart/
end
new_message
end
end
end
Save this to an .rb file anywhere under your app/ folder and it will be included.
in Application.rb you could try filtering the attachment parameter. I believe this should solve the issue, but I have not tested it myself
config.filter_parameters += [:attachment]
I've running some Cucumber/Capybara tests. I've been using the email_spec gem to check email stuff. Some steps where of the kind 'And "someone#email.com" should receive an email'. They give no problem when I run the test using the rack_test driver. However, they fail when using the selenium driver:
And "someone#email.com" should receive an email
expected: 1
got: 0 (using ==) (RSpec::Expectations::ExpectationNotMetError)
Can you help me? Thanks
You have to put your emails into a file because by default rails in test environemnt saves them in static variable that cannot be accessed from test thread. If you're using rails3 set delivery method to :file in cucumber environment. If you're on rails 2.x put this into your cucumber initializer:
ActionMailer::Base.class_eval do
DELIVERIES_CACHE_PATH =
File.join(RAILS_ROOT,'tmp','cache',"action_mailer_cache_deliveries {ENV['TEST_ENV_NUMBER']}.cache")
def perform_delivery_cache(mail)
deliveries = File.open(DELIVERIES_CACHE_PATH, 'r') do |f|
Marshal.load(f)
end
deliveries << mail
File.open(DELIVERIES_CACHE_PATH,'w') { |f| Marshal.dump(deliveries, f) }
end
def self.cached_deliveries
File.open(DELIVERIES_CACHE_PATH,'r') { |f| Marshal.load(f) }
end
end
config.action_mailer.delivery_method = :cache
Im using rspec and when i run rake spec, the user mailer sends email through smtp and not stores the email in the ActionMailer::Base.deliveries-array (invoked by an user-observer)...
Could you give me a hint why?
# Rails version
rails -v
=> Rails 3.0.1
# Ruby version with rvm (rvm version 1.0.16)
ruby -v
=> ruby 1.9.2p7 (2010-09-29 revision 29373) [x86_64-darwin10.4.0]
# Gemfile
gem "rspec-rails", ">= 2.0.1"
Config-files:
# config/environments/test.rb
MyApp::Application.configure do
config.action_mailer.delivery_method = :test
config.action_mailer.raise_delivery_errors = true
end
# spec/spec_helper.rb
...
ENV["RAILS_ENV"] ||= 'test'
...
# app/models/user_observer.rb
class UserObserver < ActiveRecord::Observer
observe User
def after_create(record)
puts Rails.env
# => "test"
UserMailer.new_user_notification(record).deliver
puts ActionMailer::Base.deliveries.inspect
# => []
# Sends it via smtp!
end
end
I'm so dumb... had "ActionMailer::Base.delivery_method = :smtp" in my setup_mail.rb-initializer... AAARGH....
To elaborate on the solution to this problem a little bit more:
In your config/enviroments/test.rb, by default you should have the line config.action_mailer.delivery_method = :test
What this does is tell ActionMailer not to send the email normally, but rather store the sent email in the array ActionMailer::Base.deliveries. This is helpful for testing because you can tell how many emails have been snt by using the inspect, count, length method on the ActionMailer::Base.deliveries array.
However, if you set the delivery method to something like config.action_mailer.delivery_method = :smtp, that could be overwriting your previous delivery_method = :test; therefore, your ActionMailer::Base.deliveries won't be populated.
I had done exactly this while using MailCatcher to view my emails being sent, thus causing my tests to fail, even though I was certain that emails were properly being sent!
So, make sure you're not setting a delivery_method other than :test in your test environment.
As a side note: If you're using Devise, you may want to check the Devise.mailer.deliveries array instead.
In my case I had
config.action_mailer.delivery_method = :test
and I also had
config.action_mailer.perform_deliveries = false
Which in effect (I suppose) doesn't send the email and also doesn't stuff it into ActionMailer::Base.deliveries array either.
Another possible gotcha...do not make the mistake, as I did, of including
ActionMailer::Base.delivery_method = :smtp
in config/environment.rb, which I incorrectly believed would be overridden by the more-specific config/environments/test.rb. Deleting the above line from config/environment.rb fixed the problem for me.
In my case, I forgot to .deliver method inside my spec.rb. It's weird, however, I get a STDOUT msg from Rails (Rails 5) of "Email delivered" even without it, which was quite confusing. Looking at your spec made me realize I forgot it. Thanks!