Brian Armstrong
Brian Armstrong

Reputation: 19873

Using rspec to test ActionMailer with should_receive

I have an RSpec test like this:

it "should ..." do
  # mailer = mock
  # mailer.should_receive(:deliver)
  Mailer.should_receive(:notification_to_sender)#.and_return(mailer)

  visit transactions_path
  expect do
    page.should_not have_css("table#transactions_list tbody tr")
    find('#some_button').click
    page.should have_css("table#transactions_list tbody tr", :count => 1)
  end.to change{Transaction.count}.by(1)
end

If I remove the commented pieces at the top, the test passes. But with the commented sections in place (how I'd expect to write it) the test fails.

I got the commented pieces off some of googling around the net, but I don't really understand what it's doing or why this fixes it. It seems like there should be a cleaner way to test emails without this.

Can anyone shed some light? Thanks!

I'm using rails 3 and rspec-rails 2.10.1

Upvotes: 3

Views: 5496

Answers (2)

MAP
MAP

Reputation: 752

You're likely calling Mailer.notification_to_sender.deliver in your controller, or better yet, a background job. I'm guessing notification_to_sender probably takes a parameter as well.

Anyways, when you call the notification_to_sender method on Mailer you're getting back an instance of Mail::Message that has the deliver method on it. If you were simply doing Mailer.notification_to_sender without also calling deliver, you could run what you have there with the comments and all would be fine. I would guess you're also calling deliver though.

In that case your failure message would be something like

NoMethodError:
  undefined method `deliver' for nil:NilClass

That is because nil is Ruby's default return value much of the time, which Rails also inherits. Without specifying the mailer = mock and .and_return(mailer) parts, when the controller executes in context of the test then notification_to_sender will return nil and the controller will try to call deliver on that nil object.

The solution you have commented out is to mock out notification_to_sender's return value (normally Mail::Message) and then expect that deliver method to be called on it.

Upvotes: 2

Matt Smith
Matt Smith

Reputation: 3529

I think you want an instance of Mailer to receive notification_to_sender not the class. From the Rails API

You never instantiate your mailer class. Rather, your delivery instance methods are automatically wrapped in class methods that start with the word deliver_ followed by the name of the mailer method that you would like to deliver. The signup_notification method defined above is delivered by invoking Notifier.deliver_signup_notification.

Therefore I would use

Mailer.any_instance.should_receive(:notification_to_sender)

Also, if you need to get the last delivered message, use

ActionMailer::Base.deliveries.last

I think that should solve your problem.

Upvotes: 2

Related Questions