Illustration of Yeti character writing with a pencil.

Using Cucumber to make sure your site is sending emails when it should

Profile picture for user Silas
Silas Albrecht
Senior Developer

I’ve been playing a bit with Cucumber lately. I needed a way to test whether my site was sending emails in certain situations. So far, I’ve found Cucumber to be a highly flexible tool for behavior-driven development. Paired up with Watir WebDriver, it controls your browser and makes sure your site functions as it should based on the test definitions you provide. Overall, it’s pretty easy to pick up, partly because Ruby is pretty elegant. Cucumber reads from “features” files. The format of these files is similar to scrum story definitions and acceptance criteria. Here’s what I used:

  Scenario: As a user, I visit service site and subscribe to the newsletter
    Given I am on the service site
    When I subscribe to the newsletter with my test email address
    Then I should receive an email with the subject
      """
      Thank you for subscribing
      """

So, that was great. I could have WebDriver go to the site and fill out the subscription form. Pretty close. But I didn’t have a way to check whether the confirmation email arrived. Ruby made that part pretty easy. Ruby comes with an IMAP class in its stdlib, and I found a Gmail library that I could borrow code from to create a class that would connect to Gmail and poll for new emails. Here’s my class:

require 'net/imap.rb'

class TestEmail
  def init_email(username, password, folder = 'inbox')
    @imap = Net::IMAP.new('imap.gmail.com', 993, true, nil, false)
    res = @imap.login(username, password)

    if res.name == 'OK'
      @logged_in = true
      @folder = folder
      @imap.select(@folder)
      @imap.search(["NOT", "SEEN"]).each do |message_id|
        @imap.store(message_id, "+FLAGS", [:Seen])
      end

      at_exit {
        if @logged_in
          @imap.logout
        end
      }
    end
  end

  def poll(retries, timeout = 5)
    while retries > 0 do
      retries -= 1

      # it does seem to be necessary to reselect this
      @imap.select(@folder)

      @imap.search(["UNSEEN"]).each do |message_id|
        @imap.store(message_id, "+FLAGS", [:Seen])
        envelope = @imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
        return envelope
      end

      sleep timeout
    end
  end
end

I call TestEmail.new and init_email() and assign the object to @mail when I setup the webdriver browser. The last step was to write the step definition to verify that we found the right email:

Then /I should receive an email with the subject/ do |text|
  email = @mail.poll(5)
  email.subject.should =~ /#{text}/m
end