Email Spec
A collection of matchers for RSpec
, MiniTest
and Cucumber
steps to make testing emails go smoothly.
This library works with ActionMailer
and Pony
. When using it with ActionMailer it works with ActiveRecord Mailer, and action_mailer_cache_delivery.
If you are testing emails in conjunction with an automated browser solution, like Selenium, you will want to use action_mailer_cache_delivery in your test environment. (This is because your test process and server processes are distinct and therefore need an intermediate store for the emails.) ActiveRecord Mailer will also work but you generally don't want to include those projects unless you need them in production.
Gem Setup
# Gemfile
group :test do
gem 'email_spec'
end
Cucumber
To use the steps in features put the following in your env.rb:
# Make sure this require is after you require cucumber/rails/world.
require 'email_spec' # add this line if you use spork
require 'email_spec/cucumber'
This will load all the helpers that the steps rely on. It will also add a Before
hook for Cucumber
so that emails are cleared at the start of each scenario.
Then:
rails generate email_spec:steps
This will give you a bunch of steps to get started with in step_definitions/email_steps.rb
By default, the generated file will look for email to [email protected]. You can either change this by editing the current_email_address
method in email_steps.rb
, or by simply specifying the target email in your features:
Scenario: A new person signs up
Given I am at "/"
When I fill in "Email" with "[email protected]"
And I press "Sign up"
Then "[email protected]" should receive an email # Specify who should receive the email
Spinach
To use the helpers and matchers in your Spinach steps, add this to your env.rb:
require 'email_spec/spinach'
Creating shared steps (as for Cucumber above) doesn't fit so well with the Spinach ethos of very compartmentalized steps, so there is no generator for Spinach. It's easy to use the helpers/matchers in your steps. For example:
step 'the last email sent should welcome the user' do
expect(last_email_sent).to have_subject('Welcome')
end
RSpec (3.1+)
First you need to require email_spec
in your spec_helper.rb
:
require "email_spec"
require "email_spec/rspec"
This will load all the helpers that the scenarios can count on. It will also add a before(:each)
hook so that emails are cleared at the start of each scenario.
If you are upgrading to Rails 5, make sure your rails_helper.rb
requires spec_helper
after loading the environment. For example:
require File.('../../config/environment', __FILE__)
require 'spec_helper'
MiniTest
First you need to require minitest-matchers and email_spec in your test_helper.rb:
require "minitest-matchers"
require "email_spec"
You will then need to include EmailSpec::Helpers and EmailSpec::Matchers in your test classes. If you want to have access to the helpers and matchers in all of your tests you can do the following in your test_helper.rb:
class MiniTest::Unit::TestCase
include EmailSpec::Helpers
include EmailSpec::Matchers
end
Otherwise, you will need to include them in the tests where you use them:
class SignupMailerTest < MiniTest::Unit::TestCase
include EmailSpec::Helpers
include EmailSpec::Matchers
...
end
Or, if you are using the MiniTest spec DSL, it would look like this:
describe SignupMailer do
include EmailSpec::Helpers
include EmailSpec::Matchers
...
end
Turnip
If you're using Turnip, you might be interested in this conversion of the Cucumber steps into Turnip steps.
Background Jobs
If you are using a background job, you might need to use a step to process the jobs. Another alternative is to use an inline statement for your scenario.
For example, for DelayedJob:
Delayed::Worker.delay_jobs = false
Usage
Cucumber
Scenario: A new person signs up
Given I am at "/"
When I fill in "Email" with "[email protected]"
And I press "Sign up"
And I should receive an email
When I open the email
Then I should see "confirm" in the email body
When I follow "confirm" in the email
Then I should see "Confirm your new account"
For more examples, check out examples/rails_root in the source for a small example app that implements these steps.
Cucumber Matchers (Ruby)
See RSpec Matchers (they are the same)
RSpec
Testing In Isolation
It is often useful to test your mailers in isolation. You can accomplish this by using mocks to verify that the mailer is being called in the correct place and then write focused examples for the actual mailer. This is a simple example from the sample app found in the gem:
Verify that the mailer is used correctly in the controller (this would apply to a model as well):
describe "POST /signup (#signup)" do
it "should deliver the signup email" do
# expect
expect(UserMailer).to(receive(:deliver_signup).with("[email protected]", "Jimmy Bean"))
# when
post :signup, "Email" => "[email protected]", "Name" => "Jimmy Bean"
end
end
Examples for the #signup method in UserMailer:
describe "Signup Email" do
include EmailSpec::Helpers
include EmailSpec::Matchers
# include ActionController::UrlWriter - old rails
include Rails.application.routes.url_helpers
before(:all) do
@email = UserMailer.create_signup("[email protected]", "Jojo Binks")
end
it "should be set to be delivered to the email passed in" do
expect(@email).to deliver_to("[email protected]")
end
it "should contain the user's message in the mail body" do
expect(@email).to have_body_text(/Jojo Binks/)
end
it "should contain a link to the confirmation link" do
expect(@email).to have_body_text(/#{confirm_account_url}/)
end
it "should have the correct subject" do
expect(@email).to have_subject(/Account confirmation/)
end
end
RSpec Matchers
reply_to(email)
alias: have_reply_to
This checks that the Reply-To header's email address (the [email protected] of "Bob Saget [email protected]") is set to the given string.
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
expect(email).to reply_to("[email protected]")
deliver_to(*email_addresses)
alias: be_delivered_to
This checks that the To header's email addresses (the [email protected] of "Bob Saget [email protected]") are set to the addresses.
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
expect(email).to deliver_to("[email protected]")
deliver_from(email)
alias: be_delivered_from
This checks that the From header's email address (the [email protected] of "Bob Saget [email protected]") is set to the given string.
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
expect(email).to deliver_from("[email protected]")
bcc_to(*email_addresses)
This checks that the BCC header's email addresses (the [email protected] of "Bob Saget [email protected]") are set to the addresses.
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
expect(email).to bcc_to("[email protected]", "[email protected]")
cc_to(*email_addresses)
This checks that the CC header's email addresses (the [email protected] of "Bob Saget [email protected]") are set to the addresses.
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
expect(email).to cc_to("[email protected]", "[email protected]")
have_subject(subject)
This checks that the Subject header's value is set to the given subject.
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
expect(email).to have_subject("Welcome!")
include_email_with_subject(subject)
Note: subject can be either a String or a Regexp
This checks that one of the given emails' subjects includes the subject.
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
email2 = UserMailer.forgot_password("[email protected]", "Jojo Binks")
expect([email, email2]).to include_email_with_subject("Welcome!")
have_body_text(text)
Note: text can be either a String or a Regexp
This checks that the text of the body has the given body.
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
expect(email).to have_body_text(/Hi Jojo Binks,/)
You can specify which part in multipart to check with in_html_part
or
in_text_part
.
email = UserMailer.("[email protected]", "Jojo Binks")
expect(email).to have_body_text(/This is html/).in_html_part
expect(email).to have_body_text(/This is text/).in_text_part
have_header(key, value)
This checks that the expected key/value pair is in the headers of the email.
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
expect(email).to have_header("X-Campaign", "1234abc")
Using the helpers when not testing in isolation
Don't. :) Seriously, if you do just take a look at the helpers and use them as you wish.
MiniTest
You will use EmailSpec in your tests the same way you use it in your specs. The only difference is the use of MiniTest's must
instead of Rspec's should
:
email = UserMailer.create_signup("[email protected]", "Jojo Binks")
email.must deliver_to("[email protected]")
Or, you can use the matcher as an expectation:
email = UserMailer.create_signup "[email protected]", "Jojo Binks"
email.must_deliver_to "[email protected]"
And of course you can use the matcher as an assertion:
email = UserMailer.create_signup "[email protected]", "Jojo Binks"
assert_must deliver_to("[email protected]"), email
Issue triage ![Open Source Helpers](https://www.codetriage.com/email-spec/email-spec/badges/users.svg)
You can contribute by triaging issues which may include reproducing bug reports or asking for vital information, such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to subscribe to email-spec on CodeTriage.
Original Authors
Ben Mabey, Aaron Gibralter, Mischa Fierer
Please see Changelog.md for upcoming changes and other contributors.