49

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?

notapatch
  • 6,569
  • 6
  • 41
  • 45
fossil12
  • 497
  • 1
  • 4
  • 9

8 Answers8

73

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.

Chowlett
  • 45,935
  • 20
  • 116
  • 150
  • 7
    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](https://github.com/bmabey/email-spec). – fossil12 Sep 08 '11 at 00:26
  • 2
    `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://stackoverflow.com/a/41180453/2771889 – thisismydesign Dec 10 '19 at 17:15
30

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.

Dennis
  • 56,821
  • 26
  • 143
  • 139
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))

Community
  • 1
  • 1
Jules Copeland
  • 1,690
  • 17
  • 22
  • 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. – thekingoftruth Jul 24 '12 at 20:27
  • 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](https://github.com/kevinrutherford/event_bus) 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). – chris raethke Nov 15 '15 at 02:38
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)
boblin
  • 3,541
  • 4
  • 25
  • 29
  • What did you mean by `delivery = double`? I don't understand that part of the code, the rest looks good, and Rails 5 compatible. – Caleb Oct 19 '16 at 04:38
  • `double` is coming from RSpec: https://relishapp.com/rspec/rspec-mocks/docs/basics/test-doubles – thisismydesign Dec 10 '19 at 14:03
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')
Alter Lagos
  • 12,090
  • 1
  • 70
  • 92
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

Jared
  • 2,885
  • 2
  • 28
  • 31
1

If you're using Capybara with Capybara Email and you sent an email to test@example.com, you can also use this method:

email = open_email('test@example.com')

And then you can test it like this:

expect(email.subject).to eq('SUBJECT')
expect(email.to).to eq(['test@example.com'])
Tomasz Giba
  • 507
  • 1
  • 8
  • 22
GuilPejon
  • 971
  • 13
  • 19
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@example.com", "Jimmy Bean"))
    # when
    post :signup, "Email" => "email@example.com", "Name" => "Jimmy Bean"
  end
end

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