How to test with RSpec if an email is delivered
Asked Answered
F

8

53

I'd like to test if an email is delivered if I call a controller method with :post. I'll use email_spec so I tried this snipped here: http://rubydoc.info/gems/email_spec/1.2.1/file/README.rdoc#Testing_In_Isolation

But it doesn't work, because I pass an instance of the model-object to the delivery-method and the instance is saved before the delivery.

I tried to create an other instance of the model-object, but then the id isn't the same.

My controller-method looks like this:

def create

   @params = params[:reservation]

   @reservation = Reservation.new(@params)
   if @reservation.save
      ReservationMailer.confirm_email(@reservation).deliver
      redirect_to success_path
   else
      @title = "Reservation"
      render 'new'
   end

end

Do you have any idea to solve this?

Fireresistant answered 2/9, 2011 at 13:50 Comment(0)
M
76

Assuming your test environment is set up in the usual fashion (that is, you have config.action_mailer.delivery_method = :test), then delivered emails are inserted into the global array ActionMailer::Base.deliveries as Mail::Message instances. You can read that from your test case and ensure the email is as expected. See here.

Meakem answered 2/9, 2011 at 14:12 Comment(2)
Thank you. It took me some time to understand it, but now I get the last mail with email = ActionMailer::Base.deliveries.last and after that I can use email with the tests from Email Spec.Fireresistant
ActionMailer::Base.deliveries is always empty for me. I had a Rails 6 project with no ActiveJob or ActionMailer configured. Followed the ActionMailer guide to set up an example, then ended up here. I'm using enqueued_jobs instead as proposed here: https://mcmap.net/q/340408/-deliver-later-not-working-in-the-test-environment-in-rails-5Planar
H
32

Configure your test environment to accumulate sent mails in ActionMailer::Base.deliveries.

# config/environments/test.rb
config.action_mailer.delivery_method = :test

Then something like this should allow you to test that the mail was sent.

# Sample parameters you would expect for POST #create.
def reservation_params
  { "reservation" => "Drinks for two at 8pm" }
end

describe MyController do
  describe "#create" do
    context "when a reservation is saved" do
      it "sends a confirmation email" do
        expect { post :create, reservation_params }.to change { ActionMailer::Base.deliveries.count }.by(1)
      end
    end
  end
end

Note that my example uses RSpec 3 syntax.

Hypsometry answered 17/10, 2014 at 16:39 Comment(0)
G
19

I know I'm late to the party with this one, but for future Googlers...

I think a better solution to this problem is answered here

The previously accepted answer is testing the Mailer itself (inside the controller spec). All you should be testing for here is that the Mailer gets told to deliver something with the right parameters.

You can then test the Mailer elsewhere to make sure it responds to those parameters correctly.

ReservationMailer.should_receive(:confirm_email).with(an_instance_of(Reservation))

Gallfly answered 16/4, 2012 at 10:35 Comment(2)
You may want to check, for example, that no emails were sent when something happens in the controller or model. You'll still have to use ActionMailer::Base.deliveries in that case. This isn't to disagree, just to be an addendum to this answer.Nabonidus
This seems like a popular answer so I thought I add that ReservationMailer.confirm_email returns a MailMessage, it doesn't send an email. You also need to check that the deliver method is called on said MailMessage. For a more elegent solution you might want to employ a Gem like EventBus so you can separate your concerns. EventBus would let you announce "reservation.created' and have a separate listener which would then send the email, asynchronously if appropriate (recommended).Safe
A
11

This is way how to test that Mailer is called with right arguments. You can use this code in feature, controller or mailer spec:

delivery = double
expect(delivery).to receive(:deliver_now).with(no_args)

expect(ReservationMailer).to receive(:confirm_email)
  .with('reservation')
  .and_return(delivery)
Achernar answered 20/8, 2015 at 19:44 Comment(2)
What did you mean by delivery = double? I don't understand that part of the code, the rest looks good, and Rails 5 compatible.Foofaraw
double is coming from RSpec: relishapp.com/rspec/rspec-mocks/docs/basics/test-doublesPlanar
C
9

Anyone using rspec +3.4 and ActiveJob to send async emails, try with:

expect {
  post :create, params
}.to have_enqueued_job.on_queue('mailers')
Chirrupy answered 29/6, 2016 at 4:22 Comment(0)
C
1

To add a little more, make sure if you're going to stub out a call using should_receive that you have an integration test elsewhere testing that you're actually calling the method correctly.

I've been bit a few times by changing a method that was tested elsewhere with should_receive and having tests still pass when the method call was broken.

If you prefer to test the outcome rather than using should_receive, shoulda has a nice matcher that works like the following:

it { should have_sent_email.with_subject(/is spam$/) }

Shoulda documentation

More information on using Shoulda Matchers with rSpec

Complacent answered 1/11, 2012 at 1:27 Comment(1)
This method is deprecated now: github.com/thoughtbot/shoulda-matchers/issues/91Corpuz
P
1

If you're using Capybara with Capybara Email and you sent an email to [email protected], you can also use this method:

email = open_email('[email protected]')

And then you can test it like this:

expect(email.subject).to eq('SUBJECT')
expect(email.to).to eq(['[email protected]'])
Pennon answered 10/11, 2017 at 17:3 Comment(0)
W
0

Try email-spec

describe "POST /signup (#signup)" do
  it "should deliver the signup email" do
    # expect
    expect(UserMailer).to(receive(:deliver_signup).with("[email protected]", "Jimmy Bean"))
    # when
    post :signup, "Email" => "[email protected]", "Name" => "Jimmy Bean"
  end
end

more examples here: https://github.com/email-spec/email-spec#testing-in-isolation

Walachia answered 14/11, 2016 at 3:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.