Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I write an Rspec controller test that makes sure an e-mail is sent?

Currently in my controller spec I have:

require 'spec_helper'

describe CustomerTicketsController do
  login_user

  describe "POST /create (#create)" do
    # include EmailSpec::Helpers
    # include EmailSpec::Matchers
    it "should deliver the sales alert email" do
      # expect
      customer_ticket_attributes = FactoryGirl.attributes_for(:customer_ticket)
      customer_mailer = mock(CustomerMailer)
      customer_mailer.should_receive(:deliver).
        with(CustomerTicket.new(customer_ticket_attributes))
      # when
      post :create, :customer_ticket => customer_ticket_attributes
    end
  end
end

In my controller I have:

  # POST /customer_tickets
  # POST /customer_tickets.xml
  def create
    respond_to do |format|
      if @customer_ticket.save
        CustomerMailer.sales_alert(@customer_ticket).deliver
        format.html { redirect_to @customer_ticket, notice: 'Customer ticket was successfully created.' }
        format.xml { render xml: @customer_ticket, status: :created, location: @customer_ticket }
      else
        format.html { render action: "new" }
        format.xml { render xml: @customer_ticket.errors, status: :unprocessable_entity }
      end
    end
  end

My test currently produces the following output:

Failures:

  1) CustomerTicketsController POST /create (#create) should deliver the sales alert email
     Failure/Error: customer_mailer.should_receive(:deliver).
       (Mock CustomerMailer).deliver(#<CustomerTicket id: nil, first_name: "firstname1", last_name: "lastname1", company: nil, referral: nil, email: "[email protected]", phone: "555-5555", fax: nil, country: nil, address1: "555 Rodeo Dr.", address2: nil, city: "Beverly Hills", state: "CA", postcode: "90210", question: "The answer to the universe is 4.", type: nil, status: nil, priority: nil, number: nil, cs_rep_id: nil, created_at: nil, updated_at: nil>)
           expected: 1 time
           received: 0 times
     # ./spec/controllers/customer_ticket_controller_spec.rb:13:in `block (3 levels) in <top (required)>'

Finished in 0.52133 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/customer_ticket_controller_spec.rb:9 # CustomerTicketsController POST /create (#create) should deliver the sales alert email

Thank you for looking.

like image 917
jklina Avatar asked Jun 04 '12 15:06

jklina


2 Answers

You could check the ActionMailer::Base.deliveries.count to ensure it has incremented by 1.

Something like this (untested)

expect {custom_mailer.deliver}.to change { ActionMailer::Base.deliveries.count }.by(1)
like image 100
Webjedi Avatar answered Oct 13 '22 18:10

Webjedi


The mocks you're setting up don't match what is actually being executed. You'd have to actually be calling deliver on that mock you're creating.

Something like

message = mock('Message')
CustomerMailer.should_receive(:sales_alert).and_return(message)
message.should_receive(:deliver)

should pass.

If you want to check what is passed to sales_alert what you're doing won't work - active record only ever compares objects by primary key equality so the customer ticket you're creating in the spec won't be equal to the one created in your controller.

One thing you could do is

CustomerMailer.should_receive(:sales_alert) do |arg|
  ...
end.and_return(message)

rspec will yield whatever sales_alert is called with and you can do whatever checks you want

Another way of doing it to is to stub out CustomerTicket.new, so that you then control what it returns, for example

mock_ticket = mock(CustomerTicket)
CustomerTicket.should_receive(:new).with(customer_ticket_attributes).and_return(mock_ticket)
mock_ticket.should_receive(:save).and_return(true)
CustomerMailer.should_receive(:sales_alert).with(mock_ticket).and_return(message)

Finally you might decide that this alert sending should really belong in an instance method of CustomerTicket in which case your controller spec could just check that a send_sales_alert method was called on the ticket.

like image 20
Frederick Cheung Avatar answered Oct 13 '22 18:10

Frederick Cheung