
We have a rails app in development that will send a lot of email so I wanted to setup a way to test that it was being delivered. I use Google for email exclusively so Gmail seemed like the obvious choice. In my integration tests I trigger an action that delivers email and then check my Gmail inbox for the email in question. Gmail exposes an atom feed that we can use for this purpose, if you view source on your inbox you should see this link in your header:
<link rel="alternate" type="application/atom+xml" title="Gmail Atom Feed" href="feed/atom" />
You can test this feed is working from the command line using curl:
curl https://username:password@mail.google.com/mail/feed/atom
In my Integration specs I wanted an easier way to access multiple Gmail accounts: enter httparty
class Gmail include HTTPartybase_uri 'https://mail.google.com'def self.inbox(user) get('/mail/feed/atom', :basic_auth => { :username => user, :password => 'X' })["feed"]["entry"] endend
IMO this is the best thing about HTTParty: the ability to take a relatively complicated API and distill it down to a simple DSL. All I have to do is drop the Gmail class into /lib and then I can use it from my integration specs:
it "sends a notification email after the model is created" do
create_model_successfully
Gmail.inbox("username").first["title"].should == Model.first.subject
end
When I create an instance of Model in my integration spec a notification email should be sent. To check that the email corresponds to the model created I use the created_at datetime as the subject to the email like this:
class Modeldef subject(text = nil) Rails.env.test? ? created_at.to_s : text endend
Depending on your network connection, you may need to add a small delay to your code to give Google a chance to receive your email before fetching the atom feed. YMMV but sleeping for 1 second before checking my inbox works reliably for me:
sleep 1
If you are concerned about how fast your tests run, or you don’t want to rely on external APIs then you will not want to run this test as part of your standard suite. You could just run this test before a deploy as a sanity check to make sure your application is working end-to-end.
If you are using Google Apps rather than Gmail for your email you will need to provide your entire email address rather than your username when authenticating. The atom feed will also be different, it will look something like this:
https://mail.google.com/a/example.com/feed/atom
If you have one unread email in your Gmail inbox then Gmail.inbox(username) will not return an array of messages. When you create a Gmail account for testing you get a couple of messages from Google by default, so just leave them unread and you will be fine.
Fadhli Rahim — March 03, 2010
Neat solution especially the part where you’re checking in your gmail inbox for the email you’d sent.
All this time I was only asserting ActionMailer for deliveries.
pete — March 12, 2010
Nice technique, I like it.
I have two problems with your testing though. The first is using “sleep 1”; I’d rather poll the atom feed and fail the test after a certain number of tries. The second is putting the Rails.env.test? in your model code. In my opinion, it would be much easier and less error-prone to mock the .subject method of your model. With flexmock it would be something like this:
it “sends a notification email after the model is created” do subject = “email notification test #{Time.now}” flexmock( Model ).new_instances.should_receive( :subject => subject ) create_model_successfully notify = nil 10.times do sleep 0.1 notify = Gmail.inbox(“username”).first[“title”] rescue nil end notify.should == subject endThere are probably more elegant ways to do this, but you get the idea.
pete — March 12, 2010
Argh, your comment form ate my formatting.
http://gist.github.com/330794
prithi aj — March 18, 2010
i hav sign in new email but not getting in the masage box
Isaac Kearse — March 18, 2010
pete: Thanks for the feedback. I like your solution to polling the atom feed, I may have to steal that :)
I’m not a big fan of mocking in general, and in this case I’m more comfortable having Rails.env.test? in the model code. Could you elaborate on why you think this would be error-prone?
Isaac Kearse — March 18, 2010
prithi aj: Could you please explain the issue you are having in more detail?
MBT Women Shoes — July 09, 2010
Yes… i also really like to visit new place, your idea is good.