13
votes

I'm using rspec/capybara/devise to conduct Integration testing in an app. One of the features of the app is the ever-populate "Account Registration" using a confirm feature (ie sign up - get an confirmation email - click on the link - account is validated).

require 'spec_helper'

describe "User Authentication" do
  describe "New user" do
    before(:each) do
      @user = Factory.build(:user)
    end

    it "can confirm account by clicking on confirmation link" do
      visit root_path
      click_link "Register"
      page.should have_content "Register for an account"
      fill_in "user_email", :with => @user.email
      fill_in "user_password", :with => @user.password
      fill_in "user_password_confirmation", :with => @user.password
      fill_in "user_first_name", :with => @user.first_name
      fill_in "user_last_name", :with => @user.last_name
      fill_in "user_city", :with => @user.city
      fill_in "user_province", :with => @user.province
      fill_in "user_country", :with => @user.country
      fill_in "user_expertise", :with => @user.expertise
      choose "user_experience_professional"
      click_button "Go!"
      last_email.to.should include(@user.email)
    end
  end
end

Here are my helpers:

module MailerMacros
  def last_email
    ActionMailer::Base.deliveries.last
  end
end

The confirmation link is in the HTML email generated. It would be lovely to be able to do something like this (assuming "Confirm My Account") is the link to the account validation.

last_email.body.find_link("Confirm My Account").click_link

Does anyone have any suggestions in being able to identify links in an email that could go into a request_spec?

Thanks

6

6 Answers

17
votes

I realize that this question is a year old, but it pointed me toward a solution that others may find helpful so I figured I would post it anyway.

I haven't fully explored the email_spec gem mentioned above so that may be a much more elegant approach, but this works quite well for my case. In my app we have devise modified to allow users to sign up with only an email address, then they have to confirm their account and are prompted for additional information. So I needed to test that the confirmations are working.

My feature file looks like this:

  Scenario: Confirm new account
    Given I have registered for an account as "[email protected]"
    When I follow the confirmation link in the confirmation email
    Then I should be able to set a password

First, I added the last_email helper to my cucumber support directory as mailer_helper.rb:

def last_email
  ActionMailer::Base.deliveries.last
end

Second, I wrote my step definitions like this:

Given(/^I have registered for an account as "(.*?)"$/) do |user_email|
  visit root_path
  click_link 'register'
  fill_in('Email', with: user_email)
  click_button 'Sign up'
  user = User.find_by_email(user_email)
  user.should_not be_nil
  user.confirmation_token.should_not be_nil
end

When(/^I follow the confirmation link in the confirmation email$/) do
  ctoken = last_email.body.match(/confirmation_token=\w*/)
  visit "/users/confirmation?#{ctoken}"
end

Then(/^I should be able to set a password$/) do
  fill_in('user[password]', with: 'passw0rd')
  fill_in('user[password_confirmation]', with: 'passw0rd')
  fill_in('user[first_name]', with: 'John')
  fill_in('user[last_name]', with: 'Doe')
  click_button 'Activate'
  page.should have_content('Your account was successfully confirmed. You are now signed in.')
  page.should have_css('div#flash_notice.alert.alert-success')
end
5
votes

You can simply do this using rspec, capybara,gem 'email_spec' and `gem 'action_mailer_cache_delivery'.

click_button "Go!"
email = open_email(@user.email)
email.should deliver_to(@user.email)
click_link "Confirm My Account"
sleep 2
4
votes

"Confirm My Account" link is auto generated, and usually bases on some kind of a token that is saved to the database.

You can make assertion, that the token for that user is included in the confirmation link that has been sent to that user. Or you may grab that link from the mail, and use visit helper method, that comes with rspec. And then check if user is confirmed.

4
votes

In case you need to do this in RSpec controller spec without introducing additional gems:

# in spec/support/controllers/mailer_helper.rb
module Controllers
  module MailerHelpers
    def last_email
      ActionMailer::Base.deliveries.last
    end

    def extract_confirmation_token(email)
      email && email.body && email.body.match(/confirmation_token=(.+)">/x)[1]
    end
  end
end

# in spec_helper.rb in RSpec.configure block
config.include Controllers::MailerHelpers, type: :controller

# in spec/controllers/confirmations_controller_spec.rb
describe ConfirmationsController do
   it "confirms registered user" do
       user.confirmation_token
       token = extract_confirmation_token last_email
       get :show, confirmation_token: token

       # fill in your expectation here
       expect(user...).to be[...]
    end
end

Hope this helps.

1
votes

Similar method to that described by @socjopata.

I do it by defining a helper in on of the specs/support/* files that will extract the token generated by devise:

def extract_token_from_email(token_name)
  mail_body = last_email.body.to_s
  mail_body[/#{token_name.to_s}_token=([^"]+)/, 1]
end

and then use by:

visit whatever_path({ some_token: token_name })

I stole that code from a GitHub gist somewhere but can't find it to reference it, so I can't take full credit.

1
votes

Following demisx's answer, I couldnt make it work without actually invoking the send_confirmation_instructions method and reloading the user object:

require 'rails_helper'

RSpec.describe Users::ConfirmationsController, type: :controller do

  describe 'ConfirmationsController' do
    before(:each) do
      @user = FactoryGirl.build(:user)
      @request.env["devise.mapping"] = Devise.mappings[:user]
    end

    it "confirms registered user" do
      @user.send_confirmation_instructions
      token = extract_confirmation_token last_email
      get :show, confirmation_token: token

      expect(@user.reload.confirmed?).to be(true)
    end
  end

end