ActionMailer not delivering confirmation emails in

2019-07-10 17:44发布

问题:

I'm using Rails (4.2.6), Ruby (2.2.4), Devise (4.1.1), Capybara (2.7.1), Capybara-email (2.5.0), Email_spec (2.1.0), Rspec (3.4.0), and Postgres (0.18.4)

After I upgraded the application from Rails 4.1.15 to 4.2.6, several authentification & registration tests fail. Before the upgrade all tests were properly passing. The code works as expected in the development environment (for example, confirmation emails are sent & viewable in the Rails server terminal). The problem of non-delivered emails only occurs in the test environment.

Here's the failing rspec ./spec/features/users/authentification_spec.rb:56:

#Sign up User

    visit "/"
    click_link "Sign up"

    find(:css, "#user_email").set("tester9@example.com")
    find(:css, "#user_password").set("password900")
    find(:css, "#user_password_confirmation").set("password900")

    expect {
            click_button "Sign up"      
        }.to change{ ActionMailer::Base.deliveries.size}.by(1)

When a user completes the form and clicks the "Sign up" button, the page redirects to the "About" page and as expected, the following flash message appears: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
Using save_and_open_page, I confirmed the aforementioned behavior. However, the spec fails with the following error:

Failure/Error:
   expect {
           click_button "Sign up"  
       }.to change{ ActionMailer::Base.deliveries.size}.by(1)

   expected result to have changed by 1, but was changed by 0
 # ./spec/features/users/authentification_spec.rb:56:in `block (2 levels) in <top (required)>'

The error indicates that there are no message objects in the ActionMailer::Base.deliveries array. The results of Pry confirm that the ActionMailer::Base.deliveries array is indeed empty:

[1] pry(main)> mail = ActionMailer::Base.deliveries
=> []

Here's the test log when the spec is run:

Started POST "/users" for 127.0.0.1 at 2016-06-09 16:16:25 -0700
Processing by RegistrationsController#create as HTML
  Parameters: {"utf8"=>"✓", "user"=>{"email"=>"tester9@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Sign up"}
  [1m[35m (0.8ms)[0m  SAVEPOINT active_record_1
  [1m[36mUser Exists (3.3ms)[0m  [1mSELECT  1 AS one FROM "users" WHERE "users"."email" = 'tester9@example.com' LIMIT 1[0m
  [1m[35mUser Exists (1.9ms)[0m  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('tester9@example.com') LIMIT 1
  [1m[36mSQL (2.1ms)[0m  [1mINSERT INTO "users" ("email", "encrypted_password", "signup_as", "created_at", "updated_at", "confirmation_token",     "confirmation_sent_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"[0m  [["email", "tester9@example.com"], ["encrypted_password", "$2a    $04$Vruf8j0A.DdOZLPe0qjVp.4PxzdR7sCdLF4FfyAr4dQSxcQAjpAwy"], ["created_at", "2016-06-09 23:16:25.095386"], ["updated_at", "2016-06-09 23:16:25.095386"],     ["confirmation_token", "EmY8JyaVAxAfiq7oQ98z"], ["confirmation_sent_at", "2016-06-09 23:16:25.097581"]]
  [1m[35m (0.4ms)[0m  RELEASE SAVEPOINT active_record_1
Redirected to http://www.example.com/about
Completed 302 Found in 113ms (ActiveRecord: 9.6ms)

The log indicates that a confirmation email was sent, but the deliveries array is empty. Why is that happening? Is the record not being committed or persisted in the test database for some reason? I've read related posts about non-delivery of mail, but have not found a solution to my problem.

Relevant code from test.rb:

  # 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
  config.action_mailer.perform_deliveries = true

Relevant contents from rails_helper.rb:

require 'rspec/rails'
require 'devise'
require 'capybara/rails'
require 'database_cleaner'
require 'capybara/poltergeist'
require 'capybara/email/rspec'
require 'email_spec'

  # Includes Devise test helpers (e.g., lets you use Devise's "sign_in" method in tests)

  config.include Devise::TestHelpers, :type => :controller 
  config.include Warden::Test::Helpers

Relevant commented-out code from devise.rb:

  # Configure the class responsible to send e-mails.
   #config.mailer = 'Devise::Mailer'

Mail delivery works as expected in development & production. What is going wrong in the test environment? How can I fix it? Thanks!

回答1:

Looking at devise - https://github.com/plataformatec/devise/blob/4-1-stable/lib/devise/models/confirmable.rb#L48 - it sends the confirmation email in an after_commit callback - If you're running with transactional testing enabled the after_commit callback will never be called (because the DB transaction is rolled back and never committed) so the email is never sent. Disable transaction based testing for that test and it will probably work.



回答2:

Just to summarize the discussion between Tom Walpole and codeinspired above into a top-level answer for browsers who might not look into comments, their solution (assuming you use DatabaseCleaner) is to make the following modifications:

rails_helper.rb

# lots of boilerplate and
# other things at the top of this file, but you'll
# eventually see....

RSpec.configure do |config|

  # ... other config stuff ...

  # sometime after this key line, you want to add:
  config.before(:each, truncation: true) do
    DatabaseCleaner.strategy = :truncation
  end

  # ... other config stuff ...

end

For the test that is in question here, you want to modify it to look like the following (note the addition of "truncation: true" in the line starting with the word "it")

something_spec.rb

# ... other stuff ...
describe "some function" do
  it "performs a function correctly", truncation: true do
    # your test
    # goes here
  end
end