Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActionMailer: Test failed but works in development environment

The ActionMailer in my Rails 3.1 project has an odd behavior, the ActionMailer::Base.deliveries is empty in test while I can actually receive the email by running the code in rails console. Anybody can points out what wrong it is?

# spec/mailers/user_mailer_spec.rb
require "spec_helper"

describe UserMailer do
  describe ".reminding_email" do
    subject { UserMailer.reminding_email(user).deliver }
    let(:user) { create :confirmed_user_with_mass_tasks }
    it "should be delivered" do
      ActionMailer::Base.deliveries.should_not be_empty
    end
    its(:to) { should == [user.email] }
    its(:subject) { should == "Task Reminding" }
  end
end

# app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
  layout "email"
  default from: Rails.env.production? ? "<production>@gmail.com" : "<test>@gmail.com"
  def reminding_email(user)
    @tasks = user.tasks.reminding_required
    @current = Time.current
    mail(to: user.email, subject: "Task Reminding")
  end
end

Failures:

1) UserMailer.reminding_email should be delivered Failure/Error: ActionMailer::Base.deliveries.should_not be_empty expected empty? to return false, got true # ./spec/mailers/user_mailer_spec.rb:8:in `block (3 levels) in '

Finished in 11.81 seconds 42 examples, 1 failure, 2 pending

# config/environments/test.rb
RemindersForMe::Application.configure do
  # Settings specified here will take precedence over those in config/application.rb

  # The test environment is used exclusively to run your application's
  # test suite.  You never need to work with it otherwise.  Remember that
  # your test database is "scratch space" for the test suite and is wiped
  # and recreated between test runs.  Don't rely on the data there!
  config.cache_classes = true

  # Configure static asset server for tests with Cache-Control for performance
  config.serve_static_assets = true
  config.static_cache_control = "public, max-age=3600"

  # Log error messages when you accidentally call methods on nil
  config.whiny_nils = true

  # Show full error reports and disable caching
  config.consider_all_requests_local       = true
  config.action_controller.perform_caching = false

  # Raise exceptions instead of rendering exception templates
  config.action_dispatch.show_exceptions = false

  # Disable request forgery protection in test environment
  config.action_controller.allow_forgery_protection    = false

  # Tell Action Mailer not to deliver emails to the real world.
  # The :test delivery method accumulates sent emails in the
  # ActionMailer::Base.deliveries array.
  config.action_mailer.delivery_method = :test

  # Use SQL instead of Active Record's schema dumper when creating the test database.
  # This is necessary if your schema can't be completely dumped by the schema dumper,
  # like if you have constraints or database-specific column types
  # config.active_record.schema_format = :sql

  # Print deprecation notices to the stderr
  config.active_support.deprecation = :stderr

  # Setup default URL options as devise needed
  config.action_mailer.default_url_options = { :host => 'localhost:3000' }
end

Application:

# config/application.rb
require File.expand_path('../boot', __FILE__)

# Pick the frameworks you want:
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
require "sprockets/railtie"
# require "rails/test_unit/railtie"

if defined?(Bundler)
  # If you precompile assets before deploying to production, use this line
  # Bundler.require(*Rails.groups(:assets => %w(development test)))
  Bundler.require *Rails.groups(:assets) if defined?(Bundler)
  # If you want your assets lazily compiled in production, use this line
  # Bundler.require(:default, :assets, Rails.env)
end

module RemindersForMe
  class Application < Rails::Application
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.

    # Custom directories with classes and modules you want to be autoloadable.
    # config.autoload_paths += %W(#{config.root}/extras)

    # Only load the plugins named here, in the order given (default is alphabetical).
    # :all can be used as a placeholder for all plugins not explicitly named.
    # config.plugins = [ :exception_notification, :ssl_requirement, :all ]

    # Activate observers that should always be running.
    # config.active_record.observers = :cacher, :garbage_collector, :forum_observer

    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    # config.time_zone = 'Central Time (US & Canada)'

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    # config.i18n.default_locale = :de

    # Configure the default encoding used in templates for Ruby 1.9.
    config.encoding = "utf-8"

    # Configure sensitive parameters which will be filtered from the log file.
    config.filter_parameters += [:password]

    # Enable the asset pipeline
    config.assets.enabled = true

    # Version of your assets, change this if you want to expire all your assets
    config.assets.version = '1.0'

    # Set Rspec generator as default
    config.generators do |g|
      g.test_framework :rspec
    end

    # Set Mailer of Devise
    config.to_prepare do
      Devise::Mailer.layout "email"
    end
  end
end

Now change the spec as follow:

require "spec_helper"

describe UserMailer do
  describe ".reminding_email" do
    subject { UserMailer.reminding_email(user) }
    let(:user) { create :confirmed_user_with_undue_tasks }
    it { expect { subject.deliver }.to change { ActionMailer::Base.deliveries.length }.by(1) }
    its(:to) { should == [user.email] }
    its(:subject) { should == "Task Reminding" }
  end
end

1) UserMailer.reminding_email Failure/Error: it { expect { subject.deliver }.to change { ActionMailer::Base.deliveries.length }.by(1) } result should have been changed by 1, but was changed by 2 # ./spec/mailers/user_mailer_spec.rb:7:in `block (3 levels) in '

like image 751
Sieg Avatar asked Jan 27 '26 01:01

Sieg


2 Answers

I had a similar problem and it happened because I was testing Devise emails: Devise uses a separate mailer, so you'll need to access Devise.mailer.deliveries instead of ActionMailer::Base.deliveries.

like image 172
Jo Liss Avatar answered Jan 28 '26 18:01

Jo Liss


You should test the separated properties of the mail:

describe UserMailer do
  describe ".reminding_email" do
    subject { UserMailer.reminding_email(user)}
    let(:user) { create :confirmed_user_with_mass_tasks }
    it{ expect{subject.deliver}.to change{ActionMailer::Base.deliveries.length}.by(1)}
    its(:to) { should == [user.email] }
    its(:subject) { should == "Task Reminding" }
  end
end

Notice the change in the subject, because your mailer as it should returns the mail method result, which already initialized with to and subject.

like image 40
Mark Huk Avatar answered Jan 28 '26 19:01

Mark Huk