-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
Feature: `send_email` matcher | ||
|
||
The `send_email` matcher is used to check if an email with the given parameters has been sent inside the expectation block. | ||
|
||
NOTE: It implies that the spec example actually sends the email using the test adapter and does not schedule it for background. | ||
|
||
To have an email sent in tests make sure: | ||
- `ActionMailer` performs deliveries - `Rails.application.config.action_mailer.perform_deliveries = true` | ||
- If the email is sent asynchronously (with `.deliver_later` call), ActiveJob uses the inline adapter - `Rails.application.config.active_job.queue_adapter = :inline` | ||
- ActionMailer uses the test adapter - `Rails.application.config.action_mailer.delivery_method = :test` | ||
|
||
If you want to check an email has been scheduled for background, use the `have_enqueued_email` matcher. | ||
|
||
Scenario: Checking email sent with the given multiple parameters | ||
Given a file named "spec/mailers/notifications_mailer_spec.rb" with: | ||
"""ruby | ||
require "rails_helper" | ||
RSpec.describe NotificationsMailer do | ||
it "checks email sending by multiple params" do | ||
expect { | ||
NotificationsMailer.signup.deliver_now | ||
}.to send_email( | ||
from: 'from@example.com', | ||
to: 'to@example.org', | ||
subject: 'Signup' | ||
) | ||
end | ||
end | ||
""" | ||
When I run `rspec spec/mailers/notifications_mailer_spec.rb` | ||
Then the examples should all pass | ||
|
||
Scenario: Checking email sent with matching parameters | ||
Given a file named "spec/mailers/notifications_mailer_spec.rb" with: | ||
"""ruby | ||
require "rails_helper" | ||
RSpec.describe NotificationsMailer do | ||
it "checks email sending by one param only" do | ||
expect { | ||
NotificationsMailer.signup.deliver_now | ||
}.to send_email( | ||
to: 'to@example.org' | ||
) | ||
end | ||
end | ||
""" | ||
When I run `rspec spec/mailers/notifications_mailer_spec.rb` | ||
Then the examples should all pass | ||
|
||
Scenario: Checking email not sent with the given parameters | ||
Given a file named "spec/mailers/notifications_mailer_spec.rb" with: | ||
"""ruby | ||
require "rails_helper" | ||
RSpec.describe NotificationsMailer do | ||
it "checks email not sent" do | ||
expect { | ||
NotificationsMailer.signup.deliver_now | ||
}.to_not send_email( | ||
to: 'no@example.org' | ||
) | ||
end | ||
end | ||
""" | ||
When I run `rspec spec/mailers/notifications_mailer_spec.rb` | ||
Then the examples should all pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
# frozen_string_literal: true | ||
|
||
module RSpec | ||
module Rails | ||
module Matchers | ||
# @api private | ||
# | ||
# Matcher class for `send_email`. Should not be instantiated directly. | ||
# | ||
# @see RSpec::Rails::Matchers#send_email | ||
class SendEmail < RSpec::Rails::Matchers::BaseMatcher | ||
# @api private | ||
# Define the email attributes that should be included in the inspection output. | ||
INSPECT_EMAIL_ATTRIBUTES = %i[subject from to cc bcc].freeze | ||
|
||
def initialize(criteria) | ||
@criteria = criteria | ||
end | ||
|
||
# @api private | ||
def supports_value_expectations? | ||
false | ||
end | ||
|
||
# @api private | ||
def supports_block_expectations? | ||
true | ||
end | ||
|
||
def matches?(block) | ||
define_matched_emails(block) | ||
|
||
@matched_emails.one? | ||
end | ||
|
||
# @api private | ||
# @return [String] | ||
def failure_message | ||
result = | ||
if multiple_match? | ||
"More than 1 matching emails were sent." | ||
else | ||
"No matching emails were sent." | ||
end | ||
"#{result}#{sent_emails_message}" | ||
end | ||
|
||
# @api private | ||
# @return [String] | ||
def failure_message_when_negated | ||
"Expected not to send an email but it was sent." | ||
end | ||
|
||
private | ||
|
||
def diffable? | ||
true | ||
end | ||
|
||
def deliveries | ||
ActionMailer::Base.deliveries | ||
end | ||
|
||
def define_matched_emails(block) | ||
before = deliveries.dup | ||
|
||
block.call | ||
|
||
after = deliveries | ||
|
||
@diff = after - before | ||
@matched_emails = @diff.select(&method(:matched_email?)) | ||
end | ||
|
||
def matched_email?(email) | ||
@criteria.all? do |attr, value| | ||
expected = | ||
case attr | ||
when :to, :from, :cc, :bcc then Array(value) | ||
else | ||
value | ||
end | ||
|
||
values_match?(expected, email.public_send(attr)) | ||
end | ||
end | ||
|
||
def multiple_match? | ||
@matched_emails.many? | ||
end | ||
|
||
def sent_emails_message | ||
if @diff.empty? | ||
"\n\nThere were no any emails sent inside the expectation block." | ||
else | ||
sent_emails = | ||
@diff.map do |email| | ||
inspected = INSPECT_EMAIL_ATTRIBUTES.map { |attr| "#{attr}: #{email.public_send(attr)}" }.join(", ") | ||
"- #{inspected}" | ||
end.join("\n") | ||
"\n\nThe following emails were sent:\n#{sent_emails}" | ||
end | ||
end | ||
end | ||
|
||
# @api public | ||
# Check email sending with specific parameters. | ||
# | ||
# @example Positive expectation | ||
# expect { action }.to send_email | ||
# | ||
# @example Negative expectations | ||
# expect { action }.not_to send_email | ||
# | ||
# @example More precise expectation with attributes to match | ||
# expect { action }.to send_email(to: 'test@example.com', subject: 'Confirm email') | ||
def send_email(criteria = {}) | ||
SendEmail.new(criteria) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
RSpec.describe "send_email" do | ||
let(:mailer) do | ||
Class.new(ActionMailer::Base) do | ||
self.delivery_method = :test | ||
|
||
def test_email | ||
mail( | ||
from: "from@example.com", | ||
cc: "cc@example.com", | ||
bcc: "bcc@example.com", | ||
to: "to@example.com", | ||
subject: "Test email", | ||
body: "Test email body" | ||
) | ||
end | ||
end | ||
end | ||
|
||
it "checks email sending by all params together" do | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email( | ||
from: "from@example.com", | ||
to: "to@example.com", | ||
cc: "cc@example.com", | ||
bcc: "bcc@example.com", | ||
subject: "Test email", | ||
body: a_string_including("Test email body") | ||
) | ||
end | ||
|
||
it "checks email sending by no params" do | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email | ||
end | ||
|
||
it "with to_not" do | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to_not send_email( | ||
from: "failed@example.com" | ||
) | ||
end | ||
|
||
it "fails with a clear message" do | ||
expect { | ||
expect { mailer.test_email.deliver_now }.to send_email(from: 'failed@example.com') | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
No matching emails were sent. | ||
The following emails were sent: | ||
- subject: Test email, from: ["from@example.com"], to: ["to@example.com"], cc: ["cc@example.com"], bcc: ["bcc@example.com"] | ||
MSG | ||
end | ||
|
||
it "fails with a clear message when no emails were sent" do | ||
expect { | ||
expect { }.to send_email | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
No matching emails were sent. | ||
There were no any emails sent inside the expectation block. | ||
MSG | ||
end | ||
|
||
it "fails with a clear message for negated version" do | ||
expect { | ||
expect { mailer.test_email.deliver_now }.to_not send_email(from: "from@example.com") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, "Expected not to send an email but it was sent.") | ||
end | ||
|
||
it "fails for multiple matches" do | ||
expect { | ||
expect { 2.times { mailer.test_email.deliver_now } }.to send_email(from: "from@example.com") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
More than 1 matching emails were sent. | ||
The following emails were sent: | ||
- subject: Test email, from: ["from@example.com"], to: ["to@example.com"], cc: ["cc@example.com"], bcc: ["bcc@example.com"] | ||
- subject: Test email, from: ["from@example.com"], to: ["to@example.com"], cc: ["cc@example.com"], bcc: ["bcc@example.com"] | ||
MSG | ||
end | ||
|
||
context "with compound matching" do | ||
it "works when both matchings pass" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email(to: "to@example.com").and send_email(from: "from@example.com") | ||
}.to_not raise_error | ||
end | ||
|
||
it "works when first matching fails" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email(to: "no@example.com").and send_email(to: "to@example.com") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
No matching emails were sent. | ||
The following emails were sent: | ||
- subject: Test email, from: ["from@example.com"], to: ["to@example.com"], cc: ["cc@example.com"], bcc: ["bcc@example.com"] | ||
MSG | ||
end | ||
|
||
it "works when second matching fails" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email(to: "to@example.com").and send_email(to: "no@example.com") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
No matching emails were sent. | ||
The following emails were sent: | ||
- subject: Test email, from: ["from@example.com"], to: ["to@example.com"], cc: ["cc@example.com"], bcc: ["bcc@example.com"] | ||
MSG | ||
end | ||
end | ||
|
||
context "with a custom negated version defined" do | ||
around do |example| | ||
RSpec::Matchers.define_negated_matcher :not_send_email, :send_email | ||
example.run | ||
ensure | ||
undef not_send_email | ||
end | ||
|
||
it "works with a negated version" do | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to not_send_email( | ||
from: "failed@example.com" | ||
) | ||
end | ||
|
||
it "fails with a clear message" do | ||
expect { | ||
expect { mailer.test_email.deliver_now }.to not_send_email(from: "from@example.com") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, "Expected not to send an email but it was sent.") | ||
end | ||
|
||
context "with a compound negated version" do | ||
it "works when both matchings pass" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to not_send_email(to: "noto@example.com").and not_send_email(from: "nofrom@example.com") | ||
}.to_not raise_error | ||
end | ||
|
||
it "works when first matching fails" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to not_send_email(to: "to@example.com").and send_email(to: "to@example.com") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, a_string_including(<<~MSG.strip)) | ||
Expected not to send an email but it was sent. | ||
MSG | ||
end | ||
|
||
it "works when second matching fails" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email(to: "to@example.com").and not_send_email(to: "to@example.com") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, a_string_including(<<~MSG.strip)) | ||
Expected not to send an email but it was sent. | ||
MSG | ||
end | ||
end | ||
end | ||
end |