Testing ActionMailer and ActionController Interaction in Rails

In the Java and .NET world, I’ve used both jMock and NMock before for this kind of thing. Essentially we’re looking at testing interaction (rather than state as we’re testing in the existing mailer unit tests)—we’re interested in knowing that the controller asks the `ActionMailer` derived-class to try and deliver a message, what happens after that has already got unit test coverage both within my app and in Rails itself, and is of no concern to the controller. We’re testing the contract that says the mailer provides a means for the controller to send an email when a certain method is invoked. Beyond that, is not for our concern in this test. And for testing interaction we can use mocks (as I alluded to earlier in the paragraph), so anyway, on with the mocks!

Rails’ convention for mocks is to place classes in the `test/mocks/test` directory, then within the application (when in the environment is set to ‘test’) the mocks will be loaded in place of the existing classes. Inside the mock you can then add a `require ‘model/myclass’` declaration and you only need to add methods you want to redefine.

However, in my case I only need to mock it out for this test, so instead I’m going to put the class re-definition at the top of my controller’s test file.

First, the controller test, which looks as follows:

def test_email_sent_to_customer_when_order_posted  @request.session[:logged_in] = "true" end

order = Order.new(:payment_result => "open")order.customer = customers(:paul)order.save!

assert_equal false, StoreMailer.postedpost :mark_as_posted, :id => order.idassert_equal true, StoreMailer.posted

We want our mock `StoreMailer` class to include a `posted` method that returns whether the `deliver_posted_mail` method was called during the test.

So, we add the following to the top of our test file:

class StoreMailer  def self.deliver_posted_receipt(email_address, order, time)    @posted = true  endend

def self.posted  @postedend

The first time I did this I had a failure inside my controller test - since this class is used in the place of the existing `StoreMailer` in all my tests, and since other tests called the same action, `deliver_posted_receipt` was being called more than once during a test run - `@posted` was thus true when it needed to be false.

So, we add a method to reset the flag inside our mock `StoreMailer`:

def self.reset_posted    @posted = falseend

Then update our test to call `reset_posted` at the beginning of the test method. Success!

We’ve now got both the mailer covered, and, crucially that it’s being used from within the controller. Mock frameworks typically also capture information about the parameters to a method so one can verify that the correct data is being passed into the method. This would be a relatively simple thing to add, maybe I’ll go do that now :)

Anyone else have any tips to share on testing interaction between classes in Rails?